@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
package/src/lib/browser-login.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { Data, Deferred, Duration, Effect } from "effect";
|
|
2
|
-
|
|
3
|
-
import { isRecord } from "./record";
|
|
4
|
-
|
|
5
|
-
interface BunServeServer {
|
|
6
|
-
readonly port: number;
|
|
7
|
-
readonly stop: (closeActiveConnections?: boolean) => void;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
interface BunRuntime {
|
|
11
|
-
readonly serve: (options: {
|
|
12
|
-
readonly hostname: string;
|
|
13
|
-
readonly port: number;
|
|
14
|
-
readonly fetch: (request: Request) => Promise<Response>;
|
|
15
|
-
readonly error: () => Response;
|
|
16
|
-
}) => BunServeServer;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class BrowserLoginTimeoutError extends Data.TaggedError("BrowserLoginTimeoutError")<{
|
|
20
|
-
readonly message: string;
|
|
21
|
-
}> {}
|
|
22
|
-
|
|
23
|
-
export class BrowserLoginSessionClosedError extends Data.TaggedError(
|
|
24
|
-
"BrowserLoginSessionClosedError",
|
|
25
|
-
)<{
|
|
26
|
-
readonly message: string;
|
|
27
|
-
}> {}
|
|
28
|
-
|
|
29
|
-
export class BrowserLoginRuntimeError extends Data.TaggedError("BrowserLoginRuntimeError")<{
|
|
30
|
-
readonly message: string;
|
|
31
|
-
}> {}
|
|
32
|
-
|
|
33
|
-
export type BrowserLoginError = BrowserLoginSessionClosedError | BrowserLoginTimeoutError;
|
|
34
|
-
|
|
35
|
-
export const CALLBACK_PAGE = `<!doctype html>
|
|
36
|
-
<html lang="en">
|
|
37
|
-
<head>
|
|
38
|
-
<meta charset="utf-8" />
|
|
39
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
40
|
-
<title>better-update CLI Login</title>
|
|
41
|
-
<style>
|
|
42
|
-
:root { color-scheme: light dark; font-family: ui-sans-serif, system-ui, sans-serif; }
|
|
43
|
-
body { margin: 0; min-height: 100vh; display: grid; place-items: center; padding: 24px; }
|
|
44
|
-
main { max-width: 32rem; line-height: 1.5; }
|
|
45
|
-
code { font-family: ui-monospace, SFMono-Regular, monospace; }
|
|
46
|
-
</style>
|
|
47
|
-
</head>
|
|
48
|
-
<body>
|
|
49
|
-
<main>
|
|
50
|
-
<h1>Completing CLI login...</h1>
|
|
51
|
-
<p id="message">Finalizing the local session. You can keep this tab open.</p>
|
|
52
|
-
</main>
|
|
53
|
-
<script>
|
|
54
|
-
const message = document.getElementById("message");
|
|
55
|
-
const render = (text) => {
|
|
56
|
-
if (message) message.textContent = text;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const params = new URLSearchParams(window.location.hash.slice(1));
|
|
60
|
-
const token = params.get("token");
|
|
61
|
-
|
|
62
|
-
if (!token) {
|
|
63
|
-
render("Missing token. Return to the CLI and run login again.");
|
|
64
|
-
} else {
|
|
65
|
-
fetch("/callback/token", {
|
|
66
|
-
method: "POST",
|
|
67
|
-
headers: { "content-type": "application/json" },
|
|
68
|
-
body: JSON.stringify({ token }),
|
|
69
|
-
})
|
|
70
|
-
.then(async (response) => {
|
|
71
|
-
if (!response.ok) {
|
|
72
|
-
const body = await response.text();
|
|
73
|
-
throw new Error(body || "Callback failed");
|
|
74
|
-
}
|
|
75
|
-
window.history.replaceState({}, document.title, window.location.pathname);
|
|
76
|
-
render("CLI login complete. You can close this tab.");
|
|
77
|
-
setTimeout(() => window.close(), 300);
|
|
78
|
-
})
|
|
79
|
-
.catch((error) => {
|
|
80
|
-
render(error instanceof Error ? error.message : "Callback failed.");
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
</script>
|
|
84
|
-
</body>
|
|
85
|
-
</html>`;
|
|
86
|
-
|
|
87
|
-
export interface BrowserLoginServer {
|
|
88
|
-
readonly callbackUrl: string;
|
|
89
|
-
readonly waitForToken: Effect.Effect<string, BrowserLoginError>;
|
|
90
|
-
readonly stop: () => void;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export interface BrowserLoginSession {
|
|
94
|
-
readonly callbackPath: string;
|
|
95
|
-
readonly waitForToken: Effect.Effect<string, BrowserLoginError>;
|
|
96
|
-
readonly handleRequest: (request: Request) => Promise<Response>;
|
|
97
|
-
readonly dispose: () => void;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export interface CreateBrowserLoginServerOptions {
|
|
101
|
-
readonly timeoutMs?: number;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export const createBrowserLoginSession = (
|
|
105
|
-
options: CreateBrowserLoginServerOptions = {},
|
|
106
|
-
): BrowserLoginSession => {
|
|
107
|
-
const tokenDeferred = Effect.runSync(Deferred.make<string, BrowserLoginSessionClosedError>());
|
|
108
|
-
const waitForToken = Deferred.await(tokenDeferred).pipe(
|
|
109
|
-
Effect.timeoutFail({
|
|
110
|
-
duration:
|
|
111
|
-
options.timeoutMs === undefined ? Duration.minutes(5) : Duration.millis(options.timeoutMs),
|
|
112
|
-
onTimeout: () =>
|
|
113
|
-
new BrowserLoginTimeoutError({
|
|
114
|
-
message: "Timed out waiting for browser login to complete.",
|
|
115
|
-
}),
|
|
116
|
-
}),
|
|
117
|
-
);
|
|
118
|
-
|
|
119
|
-
const dispose = () => {
|
|
120
|
-
Effect.runSync(
|
|
121
|
-
Deferred.fail(
|
|
122
|
-
tokenDeferred,
|
|
123
|
-
new BrowserLoginSessionClosedError({
|
|
124
|
-
message: "Browser login session closed.",
|
|
125
|
-
}),
|
|
126
|
-
),
|
|
127
|
-
);
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
callbackPath: "/callback",
|
|
132
|
-
waitForToken,
|
|
133
|
-
handleRequest: async (request) => {
|
|
134
|
-
const url = new URL(request.url);
|
|
135
|
-
|
|
136
|
-
if (request.method === "GET" && url.pathname === "/callback") {
|
|
137
|
-
return new Response(CALLBACK_PAGE, {
|
|
138
|
-
headers: { "content-type": "text/html; charset=utf-8" },
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (request.method === "POST" && url.pathname === "/callback/token") {
|
|
143
|
-
try {
|
|
144
|
-
const body: unknown = await request.json();
|
|
145
|
-
if (!isRecord(body)) {
|
|
146
|
-
return new Response("Invalid callback payload", { status: 400 });
|
|
147
|
-
}
|
|
148
|
-
const token = typeof body["token"] === "string" ? body["token"].trim() : "";
|
|
149
|
-
if (token.length === 0) {
|
|
150
|
-
return new Response("Missing token", { status: 400 });
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
Effect.runSync(Deferred.succeed(tokenDeferred, token));
|
|
154
|
-
return Response.json({ ok: true });
|
|
155
|
-
} catch {
|
|
156
|
-
return new Response("Invalid callback payload", { status: 400 });
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
return new Response("Not found", { status: 404 });
|
|
161
|
-
},
|
|
162
|
-
dispose,
|
|
163
|
-
};
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
export const createBrowserLoginServer = (
|
|
167
|
-
options: CreateBrowserLoginServerOptions = {},
|
|
168
|
-
): BrowserLoginServer => {
|
|
169
|
-
const bunRuntime = (globalThis as typeof globalThis & { readonly Bun?: BunRuntime }).Bun;
|
|
170
|
-
if (!bunRuntime) {
|
|
171
|
-
// eslint-disable-next-line functional/no-throw-statements -- sync factory surfaces an environment invariant; lifting to Effect would cascade through every caller for a condition that is effectively a precondition
|
|
172
|
-
throw new BrowserLoginRuntimeError({
|
|
173
|
-
message: "Browser login server requires the Bun runtime.",
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const session = createBrowserLoginSession(options);
|
|
178
|
-
const server = bunRuntime.serve({
|
|
179
|
-
hostname: "127.0.0.1",
|
|
180
|
-
port: 0,
|
|
181
|
-
fetch: session.handleRequest,
|
|
182
|
-
error: () => new Response("Local callback failed", { status: 500 }),
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
callbackUrl: `http://127.0.0.1:${server.port}${session.callbackPath}`,
|
|
187
|
-
waitForToken: session.waitForToken,
|
|
188
|
-
stop: () => {
|
|
189
|
-
session.dispose();
|
|
190
|
-
server.stop(true);
|
|
191
|
-
},
|
|
192
|
-
};
|
|
193
|
-
};
|
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import { it } from "@effect/vitest";
|
|
2
|
-
import { Effect, Exit } from "effect";
|
|
3
|
-
|
|
4
|
-
import { readAppMeta, readBuildProfile, readRuntimeVersionMeta } from "./build-profile";
|
|
5
|
-
import { BuildProfileError } from "./exit-codes";
|
|
6
|
-
import { failureError } from "./test-utils";
|
|
7
|
-
|
|
8
|
-
// ── fixtures ──────────────────────────────────────────────────────
|
|
9
|
-
|
|
10
|
-
const fullAppJson: Record<string, unknown> = {
|
|
11
|
-
expo: {
|
|
12
|
-
name: "my-app",
|
|
13
|
-
version: "1.2.0",
|
|
14
|
-
runtimeVersion: { policy: "fingerprint" },
|
|
15
|
-
ios: { bundleIdentifier: "com.example.app" },
|
|
16
|
-
android: { package: "com.example.app" },
|
|
17
|
-
extra: {
|
|
18
|
-
betterUpdate: {
|
|
19
|
-
projectId: "proj_123",
|
|
20
|
-
profiles: {
|
|
21
|
-
development: {
|
|
22
|
-
environment: "development",
|
|
23
|
-
ios: { buildConfiguration: "Debug", distribution: "development" },
|
|
24
|
-
android: { buildType: "debug", format: "apk" },
|
|
25
|
-
},
|
|
26
|
-
preview: {
|
|
27
|
-
environment: "preview",
|
|
28
|
-
ios: { buildConfiguration: "Release", distribution: "ad-hoc" },
|
|
29
|
-
android: { buildType: "release", format: "apk" },
|
|
30
|
-
},
|
|
31
|
-
production: {
|
|
32
|
-
environment: "production",
|
|
33
|
-
ios: { buildConfiguration: "Release", distribution: "app-store" },
|
|
34
|
-
android: { buildType: "release", format: "aab", flavor: "prod" },
|
|
35
|
-
},
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
// ── readBuildProfile ──────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
describe(readBuildProfile, () => {
|
|
45
|
-
it.effect("returns production profile with ios + android", () =>
|
|
46
|
-
Effect.gen(function* () {
|
|
47
|
-
const profile = yield* readBuildProfile(fullAppJson, "production");
|
|
48
|
-
expect(profile.name).toBe("production");
|
|
49
|
-
expect(profile.environment).toBe("production");
|
|
50
|
-
expect(profile.ios).toStrictEqual({
|
|
51
|
-
buildConfiguration: "Release",
|
|
52
|
-
distribution: "app-store",
|
|
53
|
-
});
|
|
54
|
-
expect(profile.android).toStrictEqual({
|
|
55
|
-
buildType: "release",
|
|
56
|
-
format: "aab",
|
|
57
|
-
flavor: "prod",
|
|
58
|
-
distribution: "play-store",
|
|
59
|
-
});
|
|
60
|
-
}),
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
it.effect("returns preview profile (different distribution + no flavor)", () =>
|
|
64
|
-
Effect.gen(function* () {
|
|
65
|
-
const profile = yield* readBuildProfile(fullAppJson, "preview");
|
|
66
|
-
expect(profile.ios?.distribution).toBe("ad-hoc");
|
|
67
|
-
expect(profile.android?.format).toBe("apk");
|
|
68
|
-
expect(profile.android?.flavor).toBeUndefined();
|
|
69
|
-
// Apk defaults to "direct" distribution when not explicitly set
|
|
70
|
-
expect(profile.android?.distribution).toBe("direct");
|
|
71
|
-
}),
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
it.effect("android distribution defaults: aab → play-store, apk → direct", () =>
|
|
75
|
-
Effect.gen(function* () {
|
|
76
|
-
const appJson = {
|
|
77
|
-
expo: {
|
|
78
|
-
extra: {
|
|
79
|
-
betterUpdate: {
|
|
80
|
-
profiles: {
|
|
81
|
-
aab: { android: { format: "aab", buildType: "release" } },
|
|
82
|
-
apk: { android: { format: "apk", buildType: "release" } },
|
|
83
|
-
explicit: {
|
|
84
|
-
android: {
|
|
85
|
-
format: "apk",
|
|
86
|
-
buildType: "release",
|
|
87
|
-
distribution: "play-store",
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
},
|
|
94
|
-
} as Record<string, unknown>;
|
|
95
|
-
const aab = yield* readBuildProfile(appJson, "aab");
|
|
96
|
-
const apk = yield* readBuildProfile(appJson, "apk");
|
|
97
|
-
const explicit = yield* readBuildProfile(appJson, "explicit");
|
|
98
|
-
expect(aab.android?.distribution).toBe("play-store");
|
|
99
|
-
expect(apk.android?.distribution).toBe("direct");
|
|
100
|
-
expect(explicit.android?.distribution).toBe("play-store");
|
|
101
|
-
}),
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
it.effect("rejects ios distribution 'simulator' (returns no ios section)", () =>
|
|
105
|
-
Effect.gen(function* () {
|
|
106
|
-
const appJson = {
|
|
107
|
-
expo: {
|
|
108
|
-
extra: {
|
|
109
|
-
betterUpdate: {
|
|
110
|
-
profiles: {
|
|
111
|
-
dev: { ios: { distribution: "simulator" } },
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
},
|
|
115
|
-
},
|
|
116
|
-
} as Record<string, unknown>;
|
|
117
|
-
const profile = yield* readBuildProfile(appJson, "dev");
|
|
118
|
-
expect(profile.ios).toBeUndefined();
|
|
119
|
-
}),
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
it.effect("fails with BuildProfileError when profile name missing", () =>
|
|
123
|
-
Effect.gen(function* () {
|
|
124
|
-
const exit = yield* readBuildProfile(fullAppJson, "missing").pipe(Effect.exit);
|
|
125
|
-
expect(Exit.isFailure(exit)).toBe(true);
|
|
126
|
-
if (Exit.isFailure(exit)) {
|
|
127
|
-
const error = failureError(exit);
|
|
128
|
-
expect(error).toBeInstanceOf(BuildProfileError);
|
|
129
|
-
}
|
|
130
|
-
}),
|
|
131
|
-
);
|
|
132
|
-
|
|
133
|
-
it.effect("fails with BuildProfileError when no profiles are defined", () =>
|
|
134
|
-
Effect.gen(function* () {
|
|
135
|
-
const empty = { expo: { extra: { betterUpdate: {} } } } as Record<string, unknown>;
|
|
136
|
-
const exit = yield* readBuildProfile(empty, "production").pipe(Effect.exit);
|
|
137
|
-
expect(Exit.isFailure(exit)).toBe(true);
|
|
138
|
-
}),
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
it.effect("defaults environment to production when unspecified", () =>
|
|
142
|
-
Effect.gen(function* () {
|
|
143
|
-
const appJson = {
|
|
144
|
-
expo: {
|
|
145
|
-
extra: {
|
|
146
|
-
betterUpdate: {
|
|
147
|
-
profiles: {
|
|
148
|
-
default: { ios: { distribution: "app-store" } },
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
} as Record<string, unknown>;
|
|
154
|
-
const profile = yield* readBuildProfile(appJson, "default");
|
|
155
|
-
expect(profile.environment).toBe("production");
|
|
156
|
-
}),
|
|
157
|
-
);
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
// ── readRuntimeVersionMeta ────────────────────────────────────────
|
|
161
|
-
|
|
162
|
-
describe(readRuntimeVersionMeta, () => {
|
|
163
|
-
it.effect("reads runtime version inputs without native platform sections", () =>
|
|
164
|
-
Effect.gen(function* () {
|
|
165
|
-
const appJson = {
|
|
166
|
-
expo: {
|
|
167
|
-
version: "1.0.0",
|
|
168
|
-
runtimeVersion: { policy: "fingerprint" },
|
|
169
|
-
},
|
|
170
|
-
} as Record<string, unknown>;
|
|
171
|
-
const meta = yield* readRuntimeVersionMeta(appJson);
|
|
172
|
-
expect(meta).toStrictEqual({
|
|
173
|
-
appVersion: "1.0.0",
|
|
174
|
-
rawRuntimeVersion: { policy: "fingerprint" },
|
|
175
|
-
});
|
|
176
|
-
}),
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
it.effect("fails when expo section is missing", () =>
|
|
180
|
-
Effect.gen(function* () {
|
|
181
|
-
const exit = yield* readRuntimeVersionMeta({}).pipe(Effect.exit);
|
|
182
|
-
expect(Exit.isFailure(exit)).toBe(true);
|
|
183
|
-
if (Exit.isFailure(exit)) {
|
|
184
|
-
const error = failureError(exit);
|
|
185
|
-
expect(error).toBeInstanceOf(BuildProfileError);
|
|
186
|
-
}
|
|
187
|
-
}),
|
|
188
|
-
);
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
// ── readAppMeta ───────────────────────────────────────────────────
|
|
192
|
-
|
|
193
|
-
describe(readAppMeta, () => {
|
|
194
|
-
it.effect("reads bundleId, appVersion and rawRuntimeVersion for ios", () =>
|
|
195
|
-
Effect.gen(function* () {
|
|
196
|
-
const meta = yield* readAppMeta(fullAppJson, "ios");
|
|
197
|
-
expect(meta.bundleId).toBe("com.example.app");
|
|
198
|
-
expect(meta.appVersion).toBe("1.2.0");
|
|
199
|
-
expect(meta.rawRuntimeVersion).toStrictEqual({ policy: "fingerprint" });
|
|
200
|
-
}),
|
|
201
|
-
);
|
|
202
|
-
|
|
203
|
-
it.effect("reads androidPackage for android", () =>
|
|
204
|
-
Effect.gen(function* () {
|
|
205
|
-
const meta = yield* readAppMeta(fullAppJson, "android");
|
|
206
|
-
expect(meta.androidPackage).toBe("com.example.app");
|
|
207
|
-
}),
|
|
208
|
-
);
|
|
209
|
-
|
|
210
|
-
it.effect("returns string rawRuntimeVersion as-is", () =>
|
|
211
|
-
Effect.gen(function* () {
|
|
212
|
-
const appJson = {
|
|
213
|
-
expo: {
|
|
214
|
-
version: "2.0.0",
|
|
215
|
-
runtimeVersion: "1.2.3",
|
|
216
|
-
ios: { bundleIdentifier: "com.a" },
|
|
217
|
-
android: { package: "com.a" },
|
|
218
|
-
},
|
|
219
|
-
} as Record<string, unknown>;
|
|
220
|
-
const meta = yield* readAppMeta(appJson, "ios");
|
|
221
|
-
expect(meta.rawRuntimeVersion).toBe("1.2.3");
|
|
222
|
-
}),
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
it.effect("fails when expo.ios section missing for ios platform", () =>
|
|
226
|
-
Effect.gen(function* () {
|
|
227
|
-
const appJson = {
|
|
228
|
-
expo: { version: "1.0.0", android: { package: "com.a" } },
|
|
229
|
-
} as Record<string, unknown>;
|
|
230
|
-
const exit = yield* readAppMeta(appJson, "ios").pipe(Effect.exit);
|
|
231
|
-
expect(Exit.isFailure(exit)).toBe(true);
|
|
232
|
-
if (Exit.isFailure(exit)) {
|
|
233
|
-
const error = failureError(exit);
|
|
234
|
-
expect(error).toBeInstanceOf(BuildProfileError);
|
|
235
|
-
}
|
|
236
|
-
}),
|
|
237
|
-
);
|
|
238
|
-
|
|
239
|
-
it.effect("fails when expo.android section missing for android platform", () =>
|
|
240
|
-
Effect.gen(function* () {
|
|
241
|
-
const appJson = {
|
|
242
|
-
expo: { version: "1.0.0", ios: { bundleIdentifier: "com.a" } },
|
|
243
|
-
} as Record<string, unknown>;
|
|
244
|
-
const exit = yield* readAppMeta(appJson, "android").pipe(Effect.exit);
|
|
245
|
-
expect(Exit.isFailure(exit)).toBe(true);
|
|
246
|
-
}),
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
it.effect("reads iOS buildNumber from expo.ios.buildNumber", () =>
|
|
250
|
-
Effect.gen(function* () {
|
|
251
|
-
const appJson = {
|
|
252
|
-
expo: {
|
|
253
|
-
version: "1.0.0",
|
|
254
|
-
ios: { bundleIdentifier: "com.a", buildNumber: "42" },
|
|
255
|
-
android: { package: "com.a" },
|
|
256
|
-
},
|
|
257
|
-
} as Record<string, unknown>;
|
|
258
|
-
const meta = yield* readAppMeta(appJson, "ios");
|
|
259
|
-
expect(meta.buildNumber).toBe("42");
|
|
260
|
-
}),
|
|
261
|
-
);
|
|
262
|
-
|
|
263
|
-
it.effect("reads Android buildNumber from expo.android.versionCode (numeric)", () =>
|
|
264
|
-
Effect.gen(function* () {
|
|
265
|
-
const appJson = {
|
|
266
|
-
expo: {
|
|
267
|
-
version: "1.0.0",
|
|
268
|
-
ios: { bundleIdentifier: "com.a" },
|
|
269
|
-
android: { package: "com.a", versionCode: 7 },
|
|
270
|
-
},
|
|
271
|
-
} as Record<string, unknown>;
|
|
272
|
-
const meta = yield* readAppMeta(appJson, "android");
|
|
273
|
-
expect(meta.buildNumber).toBe("7");
|
|
274
|
-
}),
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
it.effect("buildNumber is undefined when absent", () =>
|
|
278
|
-
Effect.gen(function* () {
|
|
279
|
-
const appJson = {
|
|
280
|
-
expo: {
|
|
281
|
-
version: "1.0.0",
|
|
282
|
-
ios: { bundleIdentifier: "com.a" },
|
|
283
|
-
android: { package: "com.a" },
|
|
284
|
-
},
|
|
285
|
-
} as Record<string, unknown>;
|
|
286
|
-
const meta = yield* readAppMeta(appJson, "ios");
|
|
287
|
-
expect(meta.buildNumber).toBeUndefined();
|
|
288
|
-
}),
|
|
289
|
-
);
|
|
290
|
-
});
|