@better-update/cli 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +5190 -0
- package/dist/index.mjs.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
package/tests/e2e/build.test.ts
DELETED
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
import { execFileSync } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import { mkdtempSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
|
|
7
|
-
import { setupCliE2E } from "../helpers/cli-e2e";
|
|
8
|
-
|
|
9
|
-
// Gated on ANDROID_HOME because gradlew requires a functioning Android SDK
|
|
10
|
-
// Toolchain; the build-credentials resolve endpoint is covered in the server
|
|
11
|
-
// E2E suite independently.
|
|
12
|
-
const hasAndroidSdk = Boolean(process.env["ANDROID_HOME"]);
|
|
13
|
-
|
|
14
|
-
const FIXTURE_DIR = path.resolve(import.meta.dirname, "../../../../fixtures/build-e2e-app");
|
|
15
|
-
|
|
16
|
-
const buildAppJsonTemplate = {
|
|
17
|
-
expo: {
|
|
18
|
-
name: "E2E Build App",
|
|
19
|
-
slug: "e2e-build-app",
|
|
20
|
-
owner: "e2e-build",
|
|
21
|
-
version: "1.0.0",
|
|
22
|
-
runtimeVersion: "1.0.0",
|
|
23
|
-
ios: {
|
|
24
|
-
bundleIdentifier: "com.example.e2ebuild",
|
|
25
|
-
buildNumber: "1",
|
|
26
|
-
},
|
|
27
|
-
android: {
|
|
28
|
-
package: "com.example.e2ebuild",
|
|
29
|
-
versionCode: 1,
|
|
30
|
-
},
|
|
31
|
-
extra: {
|
|
32
|
-
betterUpdate: {
|
|
33
|
-
profiles: {
|
|
34
|
-
production: {
|
|
35
|
-
environment: "production",
|
|
36
|
-
ios: { distribution: "ad-hoc" },
|
|
37
|
-
android: { distribution: "direct", format: "apk" },
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const KEYSTORE_PASSWORD = "e2epass123";
|
|
46
|
-
const KEY_ALIAS = "e2e-key";
|
|
47
|
-
const KEY_PASSWORD = "e2epass123";
|
|
48
|
-
|
|
49
|
-
const cli = setupCliE2E(".wrangler/state/e2e-cli-build", {
|
|
50
|
-
projectDir: FIXTURE_DIR,
|
|
51
|
-
appJsonTemplate: buildAppJsonTemplate,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
const buildState = {
|
|
55
|
-
buildId: "",
|
|
56
|
-
expectedByteSize: 0,
|
|
57
|
-
expectedSha256: "",
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
describe.skipIf(!hasAndroidSdk)("CLI build journey — Android", () => {
|
|
61
|
-
beforeAll(async () => {
|
|
62
|
-
if (!hasAndroidSdk) {
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
// Generate a self-signed Android keystore for signing the build.
|
|
66
|
-
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "build-e2e-keystore-"));
|
|
67
|
-
const keystorePath = path.join(tmpDir, "e2e.keystore");
|
|
68
|
-
|
|
69
|
-
execFileSync(
|
|
70
|
-
"keytool",
|
|
71
|
-
[
|
|
72
|
-
"-genkeypair",
|
|
73
|
-
"-v",
|
|
74
|
-
"-keystore",
|
|
75
|
-
keystorePath,
|
|
76
|
-
"-alias",
|
|
77
|
-
KEY_ALIAS,
|
|
78
|
-
"-keyalg",
|
|
79
|
-
"RSA",
|
|
80
|
-
"-keysize",
|
|
81
|
-
"2048",
|
|
82
|
-
"-validity",
|
|
83
|
-
"365",
|
|
84
|
-
"-storepass",
|
|
85
|
-
KEYSTORE_PASSWORD,
|
|
86
|
-
"-keypass",
|
|
87
|
-
KEY_PASSWORD,
|
|
88
|
-
"-dname",
|
|
89
|
-
"CN=E2E Test, O=Better Update",
|
|
90
|
-
],
|
|
91
|
-
{ stdio: "pipe" },
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const keystoreBlob = readFileSync(keystorePath).toString("base64");
|
|
95
|
-
rmSync(tmpDir, { recursive: true, force: true });
|
|
96
|
-
|
|
97
|
-
// Seed keystore credential on the test server via new typed endpoint.
|
|
98
|
-
const createResponse = await cli.postAuthorized("/api/android/upload-keystores", {
|
|
99
|
-
keystoreBase64: keystoreBlob,
|
|
100
|
-
keyAlias: KEY_ALIAS,
|
|
101
|
-
keystorePassword: KEYSTORE_PASSWORD,
|
|
102
|
-
keyPassword: KEY_PASSWORD,
|
|
103
|
-
});
|
|
104
|
-
expect(createResponse.status).toBe(201);
|
|
105
|
-
const createBody = await createResponse.json();
|
|
106
|
-
const keystoreId = createBody.id as string;
|
|
107
|
-
|
|
108
|
-
// Register Android application identifier + default build credentials group so the
|
|
109
|
-
// Resolve endpoint can find a keystore binding for package name.
|
|
110
|
-
const appResponse = await cli.postAuthorized(
|
|
111
|
-
`/api/projects/${cli.getProjectId()}/android-application-identifiers`,
|
|
112
|
-
{ packageName: "com.example.e2ebuild" },
|
|
113
|
-
);
|
|
114
|
-
expect(appResponse.status).toBe(201);
|
|
115
|
-
const appBody = await appResponse.json();
|
|
116
|
-
const appId = appBody.id as string;
|
|
117
|
-
|
|
118
|
-
const groupResponse = await cli.postAuthorized(
|
|
119
|
-
`/api/android-application-identifiers/${appId}/build-credentials`,
|
|
120
|
-
{
|
|
121
|
-
name: "Default",
|
|
122
|
-
isDefault: true,
|
|
123
|
-
androidUploadKeystoreId: keystoreId,
|
|
124
|
-
},
|
|
125
|
-
);
|
|
126
|
-
expect(groupResponse.status).toBe(201);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("links the fixture app to the seeded project", () => {
|
|
130
|
-
const result = cli.runCli("init");
|
|
131
|
-
expect(result.exitCode).toBe(0);
|
|
132
|
-
expect(result.stderr).toBe("");
|
|
133
|
-
expect(result.stdout).toContain("Linking project: E2E Build App (e2e-build-app)");
|
|
134
|
-
expect(result.stdout).toContain("Found existing project: E2E Build App Project");
|
|
135
|
-
expect(result.stdout).toContain("Project linked successfully");
|
|
136
|
-
|
|
137
|
-
const appJson = cli.readAppJson();
|
|
138
|
-
const expo = appJson["expo"] as Record<string, unknown>;
|
|
139
|
-
const extra = expo["extra"] as Record<string, unknown>;
|
|
140
|
-
const betterUpdate = extra["betterUpdate"] as Record<string, unknown>;
|
|
141
|
-
expect(betterUpdate["projectId"]).toBe(cli.getProjectId());
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test("builds an Android APK and uploads it", () => {
|
|
145
|
-
const result = cli.runCli("build", "--platform", "android", "--message", "E2E Android build");
|
|
146
|
-
expect(result.exitCode).toBe(0);
|
|
147
|
-
|
|
148
|
-
// Build workflow prints artifact path before upload.
|
|
149
|
-
expect(result.stdout).toContain("Artifact produced:");
|
|
150
|
-
|
|
151
|
-
// After upload, key-value summary is printed.
|
|
152
|
-
const buildIdMatch = /^Build ID\s+(.+)$/m.exec(result.stdout);
|
|
153
|
-
expect(buildIdMatch).toBeDefined();
|
|
154
|
-
buildState.buildId = buildIdMatch![1]!.trim();
|
|
155
|
-
|
|
156
|
-
expect(result.stdout).toMatch(/^Status\s+uploaded$/m);
|
|
157
|
-
expect(result.stdout).toMatch(/^Platform\s+android$/m);
|
|
158
|
-
expect(result.stdout).toMatch(/^Profile\s+production$/m);
|
|
159
|
-
expect(result.stdout).toMatch(/^Runtime version\s+1\.0\.0$/m);
|
|
160
|
-
expect(result.stdout).toMatch(/^SHA-256\s+[a-f0-9]{64}$/m);
|
|
161
|
-
expect(result.stdout).toMatch(/^Bytes\s+\d+$/m);
|
|
162
|
-
}, 600_000);
|
|
163
|
-
|
|
164
|
-
test("lists the uploaded build via CLI", () => {
|
|
165
|
-
expect(buildState.buildId).not.toBe("");
|
|
166
|
-
|
|
167
|
-
const result = cli.runCli("builds", "list");
|
|
168
|
-
expect(result.exitCode).toBe(0);
|
|
169
|
-
expect(result.stderr).toBe("");
|
|
170
|
-
expect(result.stdout).toContain(buildState.buildId);
|
|
171
|
-
expect(result.stdout).toContain("android");
|
|
172
|
-
expect(result.stdout).toContain("production");
|
|
173
|
-
expect(result.stdout).toContain("direct");
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
test("gets build details via CLI", () => {
|
|
177
|
-
const result = cli.runCli("builds", "get", buildState.buildId);
|
|
178
|
-
expect(result.exitCode).toBe(0);
|
|
179
|
-
expect(result.stderr).toBe("");
|
|
180
|
-
expect(result.stdout).toMatch(/^Platform\s+android$/m);
|
|
181
|
-
expect(result.stdout).toMatch(/^Profile\s+production$/m);
|
|
182
|
-
expect(result.stdout).toMatch(/^Distribution\s+direct$/m);
|
|
183
|
-
expect(result.stdout).toMatch(/^Runtime Version\s+1\.0\.0$/m);
|
|
184
|
-
expect(result.stdout).toMatch(/^Bundle ID\s+com\.example\.e2ebuild$/m);
|
|
185
|
-
expect(result.stdout).toMatch(/^Message\s+E2E Android build$/m);
|
|
186
|
-
expect(result.stdout).toContain("apk");
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
test("verifies build metadata in API", async () => {
|
|
190
|
-
const response = await cli.getAuthorized(`/api/builds/${buildState.buildId}`);
|
|
191
|
-
expect(response.status).toBe(200);
|
|
192
|
-
|
|
193
|
-
const build = (await response.json()) as {
|
|
194
|
-
id: string;
|
|
195
|
-
platform: string;
|
|
196
|
-
profile: string;
|
|
197
|
-
distribution: string;
|
|
198
|
-
runtimeVersion: string;
|
|
199
|
-
bundleId: string;
|
|
200
|
-
message: string;
|
|
201
|
-
artifact: { format: string; byteSize: number; sha256: string } | null;
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
expect(build.id).toBe(buildState.buildId);
|
|
205
|
-
expect(build.platform).toBe("android");
|
|
206
|
-
expect(build.profile).toBe("production");
|
|
207
|
-
expect(build.distribution).toBe("direct");
|
|
208
|
-
expect(build.runtimeVersion).toBe("1.0.0");
|
|
209
|
-
expect(build.bundleId).toBe("com.example.e2ebuild");
|
|
210
|
-
expect(build.message).toBe("E2E Android build");
|
|
211
|
-
expect(build.artifact).not.toBeNull();
|
|
212
|
-
expect(build.artifact!.format).toBe("apk");
|
|
213
|
-
expect(build.artifact!.byteSize).toBeGreaterThan(0);
|
|
214
|
-
expect(build.artifact!.sha256).toMatch(/^[a-f0-9]{64}$/);
|
|
215
|
-
|
|
216
|
-
buildState.expectedByteSize = build.artifact!.byteSize;
|
|
217
|
-
buildState.expectedSha256 = build.artifact!.sha256;
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
test("gets install link for the build", () => {
|
|
221
|
-
const result = cli.runCli("builds", "install-link", buildState.buildId);
|
|
222
|
-
expect(result.exitCode).toBe(0);
|
|
223
|
-
expect(result.stderr).toBe("");
|
|
224
|
-
expect(result.stdout).toMatch(/^Artifact URL\s+http.+$/m);
|
|
225
|
-
// Android direct distribution has no itms-services install URL.
|
|
226
|
-
expect(result.stdout).toMatch(/^Install URL\s+-$/m);
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
test("downloads the uploaded artifact and verifies its integrity", async () => {
|
|
230
|
-
expect(buildState.expectedByteSize).toBeGreaterThan(0);
|
|
231
|
-
expect(buildState.expectedSha256).not.toBe("");
|
|
232
|
-
|
|
233
|
-
// Get a fresh artifact URL via the API.
|
|
234
|
-
const linkResponse = await cli.getAuthorized(`/api/builds/${buildState.buildId}/install-link`);
|
|
235
|
-
expect(linkResponse.status).toBe(200);
|
|
236
|
-
const { artifactUrl } = (await linkResponse.json()) as { artifactUrl: string };
|
|
237
|
-
|
|
238
|
-
// Download the full artifact binary.
|
|
239
|
-
const downloadResponse = await fetch(artifactUrl);
|
|
240
|
-
expect(downloadResponse.status).toBe(200);
|
|
241
|
-
|
|
242
|
-
const body = Buffer.from(await downloadResponse.arrayBuffer());
|
|
243
|
-
expect(body.byteLength).toBe(buildState.expectedByteSize);
|
|
244
|
-
|
|
245
|
-
const actualSha256 = createHash("sha256").update(body).digest("hex");
|
|
246
|
-
expect(actualSha256).toBe(buildState.expectedSha256);
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
test("deletes the build and confirms removal", () => {
|
|
250
|
-
const deleteResult = cli.runCli("builds", "delete", buildState.buildId);
|
|
251
|
-
expect(deleteResult.exitCode).toBe(0);
|
|
252
|
-
expect(deleteResult.stderr).toBe("");
|
|
253
|
-
expect(deleteResult.stdout).toContain(`Build ${buildState.buildId} deleted.`);
|
|
254
|
-
|
|
255
|
-
const listResult = cli.runCli("builds", "list");
|
|
256
|
-
expect(listResult.exitCode).toBe(0);
|
|
257
|
-
expect(listResult.stdout).not.toContain(buildState.buildId);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test("builds with --no-upload and skips the upload step", () => {
|
|
261
|
-
const result = cli.runCli("build", "--platform", "android", "--no-upload");
|
|
262
|
-
expect(result.exitCode).toBe(0);
|
|
263
|
-
|
|
264
|
-
expect(result.stdout).toContain("Artifact produced:");
|
|
265
|
-
expect(result.stdout).toMatch(/^Artifact\s+.+\.apk$/m);
|
|
266
|
-
expect(result.stdout).toMatch(/^SHA-256\s+[a-f0-9]{64}$/m);
|
|
267
|
-
expect(result.stdout).toMatch(/^Bytes\s+\d+$/m);
|
|
268
|
-
expect(result.stdout).toMatch(/^Upload\s+skipped \(--no-upload\)$/m);
|
|
269
|
-
}, 600_000);
|
|
270
|
-
});
|