@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.
- package/dist/index.js +5319 -0
- package/dist/index.js.map +1 -0
- package/package.json +12 -9
- package/CHANGELOG.md +0 -58
- package/oxlint.config.ts +0 -6
- package/src/app-layer.ts +0 -29
- package/src/application/build-workflow.ts +0 -222
- package/src/application/command-exit.ts +0 -13
- package/src/application/login.ts +0 -87
- package/src/application/update-promote.ts +0 -88
- package/src/application/update-publish.ts +0 -402
- package/src/application/update-rollback.ts +0 -275
- package/src/commands/analytics/adoption.ts +0 -40
- package/src/commands/analytics/channels.ts +0 -35
- package/src/commands/analytics/helpers.ts +0 -3
- package/src/commands/analytics/index.ts +0 -13
- package/src/commands/analytics/platforms.ts +0 -39
- package/src/commands/analytics/updates.ts +0 -35
- package/src/commands/audit-logs/helpers.ts +0 -3
- package/src/commands/audit-logs/index.ts +0 -8
- package/src/commands/audit-logs/list.ts +0 -66
- package/src/commands/branches.ts +0 -70
- package/src/commands/build/android.ts +0 -129
- package/src/commands/build/index.ts +0 -63
- package/src/commands/build/ios.ts +0 -199
- package/src/commands/build/reserve-and-upload.test.ts +0 -263
- package/src/commands/build/reserve-and-upload.ts +0 -160
- package/src/commands/build/run-step.ts +0 -131
- package/src/commands/builds/compatibility-matrix.ts +0 -48
- package/src/commands/builds/delete.ts +0 -15
- package/src/commands/builds/get.ts +0 -34
- package/src/commands/builds/helpers.ts +0 -3
- package/src/commands/builds/index.ts +0 -20
- package/src/commands/builds/install-link.ts +0 -20
- package/src/commands/builds/list.ts +0 -38
- package/src/commands/channels/create.ts +0 -37
- package/src/commands/channels/delete.ts +0 -15
- package/src/commands/channels/helpers.ts +0 -18
- package/src/commands/channels/index.ts +0 -24
- package/src/commands/channels/list.ts +0 -38
- package/src/commands/channels/pause.ts +0 -15
- package/src/commands/channels/resume.ts +0 -15
- package/src/commands/channels/rollout/complete.ts +0 -17
- package/src/commands/channels/rollout/create.ts +0 -36
- package/src/commands/channels/rollout/index.ts +0 -11
- package/src/commands/channels/rollout/revert.ts +0 -17
- package/src/commands/channels/rollout/update.ts +0 -23
- package/src/commands/channels/update.ts +0 -32
- package/src/commands/credentials/delete.ts +0 -24
- package/src/commands/credentials/index.ts +0 -10
- package/src/commands/credentials/list.ts +0 -33
- package/src/commands/credentials/upload.ts +0 -91
- package/src/commands/env/delete.ts +0 -35
- package/src/commands/env/export.ts +0 -27
- package/src/commands/env/get.ts +0 -25
- package/src/commands/env/helpers.ts +0 -13
- package/src/commands/env/import.ts +0 -31
- package/src/commands/env/index.ts +0 -24
- package/src/commands/env/list.ts +0 -44
- package/src/commands/env/pull.ts +0 -27
- package/src/commands/env/set.ts +0 -42
- package/src/commands/fingerprint/compare.ts +0 -25
- package/src/commands/fingerprint/generate.ts +0 -18
- package/src/commands/fingerprint/index.ts +0 -9
- package/src/commands/init.ts +0 -35
- package/src/commands/login.ts +0 -13
- package/src/commands/logout.ts +0 -12
- package/src/commands/projects.ts +0 -84
- package/src/commands/status.ts +0 -48
- package/src/commands/update/delete.ts +0 -15
- package/src/commands/update/helpers.ts +0 -22
- package/src/commands/update/index.ts +0 -22
- package/src/commands/update/list.ts +0 -60
- package/src/commands/update/promote.ts +0 -30
- package/src/commands/update/publish.ts +0 -94
- package/src/commands/update/rollback.ts +0 -42
- package/src/commands/update/rollout/complete.ts +0 -17
- package/src/commands/update/rollout/index.ts +0 -10
- package/src/commands/update/rollout/revert.ts +0 -17
- package/src/commands/update/rollout/set.ts +0 -23
- package/src/index.ts +0 -53
- package/src/lib/android-keystore.test.ts +0 -114
- package/src/lib/android-keystore.ts +0 -76
- package/src/lib/android-signing-gradle.test.ts +0 -95
- package/src/lib/android-signing-gradle.ts +0 -52
- package/src/lib/app-json.ts +0 -81
- package/src/lib/apple-auth.test.ts +0 -402
- package/src/lib/apple-auth.ts +0 -132
- package/src/lib/artifact-finder.test.ts +0 -195
- package/src/lib/artifact-finder.ts +0 -122
- package/src/lib/browser-login.test.ts +0 -88
- package/src/lib/browser-login.ts +0 -193
- package/src/lib/build-profile.test.ts +0 -290
- package/src/lib/build-profile.ts +0 -234
- package/src/lib/cli-schemas.ts +0 -39
- package/src/lib/command-errors.ts +0 -60
- package/src/lib/credentials-downloader.ts +0 -181
- package/src/lib/credentials-manager.ts +0 -354
- package/src/lib/env-exporter.test.ts +0 -96
- package/src/lib/env-exporter.ts +0 -28
- package/src/lib/exit-codes.ts +0 -82
- package/src/lib/expo-config.ts +0 -130
- package/src/lib/expo-export.test.ts +0 -94
- package/src/lib/expo-export.ts +0 -281
- package/src/lib/fingerprint.ts +0 -67
- package/src/lib/format-error.ts +0 -22
- package/src/lib/git-context.ts +0 -56
- package/src/lib/gradle-config.ts +0 -126
- package/src/lib/ios-export-options.test.ts +0 -98
- package/src/lib/ios-export-options.ts +0 -62
- package/src/lib/ios-keychain.ts +0 -181
- package/src/lib/ios-provisioning.test.ts +0 -115
- package/src/lib/ios-provisioning.ts +0 -179
- package/src/lib/output.ts +0 -32
- package/src/lib/pkcs12.ts +0 -73
- package/src/lib/plist.ts +0 -39
- package/src/lib/post-build-validation.ts +0 -146
- package/src/lib/presigned-upload.test.ts +0 -140
- package/src/lib/presigned-upload.ts +0 -35
- package/src/lib/record.ts +0 -5
- package/src/lib/resolve-named-resource.ts +0 -24
- package/src/lib/runtime-version.test.ts +0 -119
- package/src/lib/runtime-version.ts +0 -62
- package/src/lib/sha256.test.ts +0 -108
- package/src/lib/sha256.ts +0 -80
- package/src/lib/signed-payloads.test.ts +0 -181
- package/src/lib/signed-payloads.ts +0 -164
- package/src/lib/string-utils.ts +0 -4
- package/src/lib/temp-dir.ts +0 -14
- package/src/lib/test-utils.ts +0 -13
- package/src/lib/update-platforms.test.ts +0 -45
- package/src/lib/update-platforms.ts +0 -19
- package/src/lib/xcpretty-formatter.ts +0 -21
- package/src/services/api-client.ts +0 -42
- package/src/services/apple-session-store.ts +0 -100
- package/src/services/auth-store.ts +0 -85
- package/src/services/cli-runtime.ts +0 -46
- package/src/services/config-store.ts +0 -108
- package/src/services/presigned-upload.ts +0 -84
- package/src/services/update-asset-uploader.ts +0 -72
- package/src/types/keychain.d.ts +0 -22
- package/tests/e2e/build.test.ts +0 -270
- package/tests/e2e/commands.test.ts +0 -694
- package/tests/e2e/ota-lifecycle.test.ts +0 -275
- package/tests/e2e/publish.test.ts +0 -150
- package/tests/helpers/cli-e2e.ts +0 -426
- package/tests/helpers/pty-driver.ts +0 -142
- package/tests/interactive/harness/provider-prompt.ts +0 -54
- package/tests/interactive/login.test.ts +0 -47
- package/tests/interactive/provider-select.test.ts +0 -59
- package/tsconfig.json +0 -7
- 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
|
-
);
|