@better-update/cli 0.3.2 → 0.4.1
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 → index.mjs} +495 -624
- package/dist/index.mjs.map +1 -0
- package/package.json +3 -2
- package/dist/index.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createRequire } from "module";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import { Args, Command, Options, Prompt } from "@effect/cli";
|
|
5
5
|
import { NodeContext, NodeRuntime } from "@effect/platform-node";
|
|
@@ -15,7 +15,7 @@ import { ExpoRunFormatter } from "@expo/xcpretty";
|
|
|
15
15
|
import { getFormattedSerialNumber, getX509Certificate, parsePKCS12 } from "@expo/pkcs12";
|
|
16
16
|
import { createServer } from "node:http";
|
|
17
17
|
|
|
18
|
-
//#region
|
|
18
|
+
//#region \0rolldown/runtime.js
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
//#endregion
|
|
@@ -26,6 +26,7 @@ var AuthContext = class extends Context.Tag("api/AuthContext")() {};
|
|
|
26
26
|
//#region ../../packages/api/src/auth/errors.ts
|
|
27
27
|
var Unauthorized = class extends Schema.TaggedError()("Unauthorized", { message: Schema.String }, HttpApiSchema.annotations({ status: 401 })) {};
|
|
28
28
|
var Forbidden = class extends Schema.TaggedError()("Forbidden", { message: Schema.String }, HttpApiSchema.annotations({ status: 403 })) {};
|
|
29
|
+
var OrgRequired = class extends Schema.TaggedError()("OrgRequired", { message: Schema.String }, HttpApiSchema.annotations({ status: 400 })) {};
|
|
29
30
|
|
|
30
31
|
//#endregion
|
|
31
32
|
//#region ../../packages/api/src/auth/middleware.ts
|
|
@@ -157,6 +158,7 @@ const DeleteAndroidApplicationIdentifierResult = Schema.Struct({ deleted: Schema
|
|
|
157
158
|
//#region ../../packages/api/src/domain/errors.ts
|
|
158
159
|
var BadRequest = class extends Schema.TaggedError()("BadRequest", { message: Schema.String }, HttpApiSchema.annotations({ status: 400 })) {};
|
|
159
160
|
var Conflict = class extends Schema.TaggedError()("Conflict", { message: Schema.String }, HttpApiSchema.annotations({ status: 409 })) {};
|
|
161
|
+
var NotAcceptable = class extends Schema.TaggedError()("NotAcceptable", { message: Schema.String }, HttpApiSchema.annotations({ status: 406 })) {};
|
|
160
162
|
|
|
161
163
|
//#endregion
|
|
162
164
|
//#region ../../packages/api/src/groups/android-application-identifiers.ts
|
|
@@ -712,6 +714,15 @@ var Build = class extends Schema.Class("Build")({
|
|
|
712
714
|
metadataJson: Schema.String,
|
|
713
715
|
createdAt: DateTimeString
|
|
714
716
|
}) {};
|
|
717
|
+
var BuildArtifact = class extends Schema.Class("BuildArtifact")({
|
|
718
|
+
buildId: Id,
|
|
719
|
+
r2Key: Schema.String,
|
|
720
|
+
format: ArtifactFormat,
|
|
721
|
+
contentType: Schema.String,
|
|
722
|
+
byteSize: Schema.Number,
|
|
723
|
+
sha256: Schema.String,
|
|
724
|
+
createdAt: DateTimeString
|
|
725
|
+
}) {};
|
|
715
726
|
var BuildWithArtifact = class extends Build.extend("BuildWithArtifact")({ artifact: Schema.NullOr(Schema.Struct({
|
|
716
727
|
r2Key: Schema.String,
|
|
717
728
|
format: ArtifactFormat,
|
|
@@ -1331,6 +1342,26 @@ var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGro
|
|
|
1331
1342
|
//#endregion
|
|
1332
1343
|
//#region ../../packages/api/src/groups/manifest.ts
|
|
1333
1344
|
const projectIdParam = HttpApiSchema.param("projectId", Schema.String);
|
|
1345
|
+
var ManifestGroup = class extends HttpApiGroup.make("manifest").add(HttpApiEndpoint.get("serve")`/manifest/${projectIdParam}`.addError(BadRequest).addError(NotFound).addError(NotAcceptable).annotateContext(OpenApi.annotations({
|
|
1346
|
+
title: "Serve manifest",
|
|
1347
|
+
description: "Expo Updates protocol v1 manifest endpoint"
|
|
1348
|
+
}))).annotateContext(OpenApi.annotations({
|
|
1349
|
+
title: "Protocol",
|
|
1350
|
+
description: "Expo Updates protocol endpoints"
|
|
1351
|
+
})) {};
|
|
1352
|
+
|
|
1353
|
+
//#endregion
|
|
1354
|
+
//#region ../../packages/api/src/protocol-api.ts
|
|
1355
|
+
/**
|
|
1356
|
+
* Documentation-only contract for OpenAPI/Scalar generation.
|
|
1357
|
+
* The manifest endpoint bypasses HttpApiBuilder at runtime because the Expo Updates
|
|
1358
|
+
* protocol requires multipart/mixed responses that do not fit the standard pipeline.
|
|
1359
|
+
*/
|
|
1360
|
+
var ProtocolApi = class extends HttpApi.make("protocol-api").add(ManifestGroup).annotateContext(OpenApi.annotations({
|
|
1361
|
+
title: "Better Update Protocol API",
|
|
1362
|
+
version: "1.0.0",
|
|
1363
|
+
description: "Expo Updates protocol endpoints (unauthenticated)"
|
|
1364
|
+
})) {};
|
|
1334
1365
|
|
|
1335
1366
|
//#endregion
|
|
1336
1367
|
//#region src/lib/exit-codes.ts
|
|
@@ -1361,9 +1392,9 @@ const formatCause = (cause) => {
|
|
|
1361
1392
|
if (typeof cause === "object" && cause !== null) {
|
|
1362
1393
|
const tagged = cause;
|
|
1363
1394
|
const tag = typeof tagged._tag === "string" ? tagged._tag : void 0;
|
|
1364
|
-
const message
|
|
1365
|
-
if (tag && message
|
|
1366
|
-
if (message
|
|
1395
|
+
const message = typeof tagged.message === "string" ? tagged.message : void 0;
|
|
1396
|
+
if (tag && message) return `${tag}: ${message}`;
|
|
1397
|
+
if (message) return message;
|
|
1367
1398
|
if (tag) return tag;
|
|
1368
1399
|
}
|
|
1369
1400
|
return String(cause);
|
|
@@ -1382,7 +1413,7 @@ const CliRuntimeLive = Layer.succeed(CliRuntime, {
|
|
|
1382
1413
|
argv: [...process.argv],
|
|
1383
1414
|
platform: process.platform,
|
|
1384
1415
|
cwd: Effect.sync(() => process.cwd()),
|
|
1385
|
-
getEnv: (name
|
|
1416
|
+
getEnv: (name) => Effect.sync(() => process.env[name]),
|
|
1386
1417
|
homeDirectory: Effect.sync(() => process.env["HOME"] ?? process.env["USERPROFILE"] ?? process.cwd()),
|
|
1387
1418
|
userName: Effect.sync(() => process.env["USER"] ?? process.env["USERNAME"] ?? "better-update"),
|
|
1388
1419
|
commandEnvironment: (overrides = {}) => Effect.sync(() => ({
|
|
@@ -1429,24 +1460,10 @@ const AuthStoreLive = Layer.effect(AuthStore, Effect.gen(function* () {
|
|
|
1429
1460
|
|
|
1430
1461
|
//#endregion
|
|
1431
1462
|
//#region src/services/config-store.ts
|
|
1432
|
-
const DEFAULT_BASE_URL = "https://
|
|
1433
|
-
const
|
|
1463
|
+
const DEFAULT_BASE_URL = "https://graph.better-update.dev";
|
|
1464
|
+
const DEFAULT_ACCOUNTS_URL = "https://accounts.better-update.dev";
|
|
1434
1465
|
var ConfigStoreParseError = class extends Data.TaggedError("ConfigStoreParseError") {};
|
|
1435
1466
|
const normalizeUrl = (value) => value.replace(/\/$/, "");
|
|
1436
|
-
const deriveDashboardUrl = (serverUrl) => {
|
|
1437
|
-
const normalized = normalizeUrl(serverUrl);
|
|
1438
|
-
if (!URL.canParse(normalized)) return DEFAULT_DASHBOARD_URL;
|
|
1439
|
-
const url = new URL(normalized);
|
|
1440
|
-
if (url.hostname.startsWith("api.")) {
|
|
1441
|
-
url.hostname = url.hostname.slice(4);
|
|
1442
|
-
return normalizeUrl(url.toString());
|
|
1443
|
-
}
|
|
1444
|
-
if (url.pathname === "/api") {
|
|
1445
|
-
url.pathname = "/";
|
|
1446
|
-
return normalizeUrl(url.toString());
|
|
1447
|
-
}
|
|
1448
|
-
return normalized;
|
|
1449
|
-
};
|
|
1450
1467
|
var ConfigStore = class extends Context.Tag("cli/ConfigStore")() {};
|
|
1451
1468
|
const ConfigStoreLive = Layer.effect(ConfigStore, Effect.gen(function* () {
|
|
1452
1469
|
const fs = yield* FileSystem.FileSystem;
|
|
@@ -1460,31 +1477,27 @@ const ConfigStoreLive = Layer.effect(ConfigStore, Effect.gen(function* () {
|
|
|
1460
1477
|
cause
|
|
1461
1478
|
})
|
|
1462
1479
|
}).pipe(Effect.map((parsed) => isRecord(parsed) ? parsed : void 0), Effect.catchAll(() => Effect.succeed(void 0)))));
|
|
1463
|
-
const resolveBaseUrl = Effect.gen(function* () {
|
|
1464
|
-
const envUrl = yield* runtime.getEnv("BETTER_UPDATE_URL");
|
|
1465
|
-
if (envUrl) return normalizeUrl(envUrl);
|
|
1466
|
-
const parsed = yield* readConfig;
|
|
1467
|
-
const serverUrl = parsed?.["serverUrl"];
|
|
1468
|
-
if (typeof serverUrl === "string") return normalizeUrl(serverUrl);
|
|
1469
|
-
return DEFAULT_BASE_URL;
|
|
1470
|
-
});
|
|
1471
1480
|
return {
|
|
1472
|
-
getBaseUrl:
|
|
1473
|
-
|
|
1474
|
-
const envUrl = yield* runtime.getEnv("BETTER_UPDATE_DASHBOARD_URL");
|
|
1481
|
+
getBaseUrl: Effect.gen(function* () {
|
|
1482
|
+
const envUrl = yield* runtime.getEnv("BETTER_UPDATE_URL");
|
|
1475
1483
|
if (envUrl) return normalizeUrl(envUrl);
|
|
1476
|
-
const
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1484
|
+
const baseUrl = (yield* readConfig)?.["baseUrl"];
|
|
1485
|
+
if (typeof baseUrl === "string") return normalizeUrl(baseUrl);
|
|
1486
|
+
return DEFAULT_BASE_URL;
|
|
1487
|
+
}),
|
|
1488
|
+
getAccountsUrl: Effect.gen(function* () {
|
|
1489
|
+
const envUrl = yield* runtime.getEnv("BETTER_UPDATE_ACCOUNTS_URL");
|
|
1490
|
+
if (envUrl) return normalizeUrl(envUrl);
|
|
1491
|
+
const accountsUrl = (yield* readConfig)?.["accountsUrl"];
|
|
1492
|
+
if (typeof accountsUrl === "string") return normalizeUrl(accountsUrl);
|
|
1493
|
+
return DEFAULT_ACCOUNTS_URL;
|
|
1481
1494
|
})
|
|
1482
1495
|
};
|
|
1483
1496
|
}));
|
|
1484
1497
|
|
|
1485
1498
|
//#endregion
|
|
1486
1499
|
//#region src/services/api-client.ts
|
|
1487
|
-
|
|
1500
|
+
HttpApiClient.make(ManagementApi);
|
|
1488
1501
|
var ApiClientService = class extends Context.Tag("cli/ApiClient")() {};
|
|
1489
1502
|
const apiClient = Effect.flatMap(ApiClientService, ({ get }) => get);
|
|
1490
1503
|
const ApiClientLive = Layer.effect(ApiClientService, Effect.gen(function* () {
|
|
@@ -1514,8 +1527,7 @@ const safeJsonParse = (text) => Effect.runSync(Effect.orElseSucceed(Effect.try({
|
|
|
1514
1527
|
var AppleSessionStore = class extends Context.Tag("cli/AppleSessionStore")() {};
|
|
1515
1528
|
const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(function* () {
|
|
1516
1529
|
const fs = yield* FileSystem.FileSystem;
|
|
1517
|
-
const
|
|
1518
|
-
const homeDirectory = yield* runtime.homeDirectory;
|
|
1530
|
+
const homeDirectory = yield* (yield* CliRuntime).homeDirectory;
|
|
1519
1531
|
const sessionDir = path.join(homeDirectory, ".better-update");
|
|
1520
1532
|
const sessionFile = path.join(sessionDir, "apple-session.json");
|
|
1521
1533
|
return {
|
|
@@ -1527,14 +1539,12 @@ const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(functio
|
|
|
1527
1539
|
if (typeof parsed["teamId"] !== "string" || typeof parsed["username"] !== "string" || !parsed["cookies"]) return null;
|
|
1528
1540
|
const providerIdRaw = parsed["providerId"];
|
|
1529
1541
|
const hasProviderId = typeof providerIdRaw === "number" && Number.isInteger(providerIdRaw);
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
cookies,
|
|
1542
|
+
return {
|
|
1543
|
+
cookies: parsed["cookies"],
|
|
1533
1544
|
teamId: parsed["teamId"],
|
|
1534
1545
|
username: parsed["username"],
|
|
1535
1546
|
...hasProviderId ? { providerId: providerIdRaw } : {}
|
|
1536
1547
|
};
|
|
1537
|
-
return session;
|
|
1538
1548
|
}),
|
|
1539
1549
|
saveSession: (session) => Effect.gen(function* () {
|
|
1540
1550
|
yield* fs.makeDirectory(sessionDir, { recursive: true });
|
|
@@ -1551,7 +1561,7 @@ const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(functio
|
|
|
1551
1561
|
const EXPIRY_SAFETY_MARGIN_MS = 3e4;
|
|
1552
1562
|
var PresignedUploadClient = class extends Context.Tag("cli/PresignedUploadClient")() {};
|
|
1553
1563
|
const PresignedUploadClientLive = Layer.effect(PresignedUploadClient, Effect.gen(function* () {
|
|
1554
|
-
const client
|
|
1564
|
+
const client = yield* HttpClient.HttpClient;
|
|
1555
1565
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
1556
1566
|
return { putToPresignedUrl: ({ url, filePath, byteSize, expiresAt, headers }) => Effect.gen(function* () {
|
|
1557
1567
|
const now = Date.now();
|
|
@@ -1561,12 +1571,11 @@ const PresignedUploadClientLive = Layer.effect(PresignedUploadClient, Effect.gen
|
|
|
1561
1571
|
"content-length": String(byteSize),
|
|
1562
1572
|
...headers
|
|
1563
1573
|
})), Effect.mapError((cause) => new UploadFailedError({ message: `Failed to open artifact for upload: ${String(cause)}` })));
|
|
1564
|
-
const response = yield* client
|
|
1574
|
+
const response = yield* client.execute(request).pipe(Effect.mapError((cause) => new UploadFailedError({ message: `HTTP request to presigned URL failed: ${String(cause)}` })));
|
|
1565
1575
|
if (response.status < 200 || response.status >= 300) {
|
|
1566
1576
|
const body = yield* response.text.pipe(Effect.orElseSucceed(() => ""));
|
|
1567
1577
|
return yield* new UploadFailedError({ message: `Presigned URL upload failed with status ${response.status}: ${body}` });
|
|
1568
1578
|
}
|
|
1569
|
-
return void 0;
|
|
1570
1579
|
}) };
|
|
1571
1580
|
}));
|
|
1572
1581
|
|
|
@@ -1602,8 +1611,7 @@ const CliLive = Layer.mergeAll(CliAdapterDependencies, ApiClientLayer, Presigned
|
|
|
1602
1611
|
//#endregion
|
|
1603
1612
|
//#region src/lib/app-json.ts
|
|
1604
1613
|
const readAppJson = Effect.gen(function* () {
|
|
1605
|
-
const
|
|
1606
|
-
const content = yield* fs.readFileString("./app.json").pipe(Effect.mapError(() => new ProjectNotLinkedError({ message: "app.json not found in current directory" })));
|
|
1614
|
+
const content = yield* (yield* FileSystem.FileSystem).readFileString("./app.json").pipe(Effect.mapError(() => new ProjectNotLinkedError({ message: "app.json not found in current directory" })));
|
|
1607
1615
|
const parsed = yield* Effect.try({
|
|
1608
1616
|
try: () => JSON.parse(content),
|
|
1609
1617
|
catch: () => new ProjectNotLinkedError({ message: "app.json contains malformed JSON" })
|
|
@@ -1612,28 +1620,22 @@ const readAppJson = Effect.gen(function* () {
|
|
|
1612
1620
|
return parsed;
|
|
1613
1621
|
});
|
|
1614
1622
|
const readProjectId = Effect.gen(function* () {
|
|
1615
|
-
const
|
|
1616
|
-
const expo = asRecord(appJson["expo"]);
|
|
1617
|
-
const extra = asRecord(expo?.["extra"]);
|
|
1618
|
-
const betterUpdate = asRecord(extra?.["betterUpdate"]);
|
|
1619
|
-
const projectId = betterUpdate?.["projectId"];
|
|
1623
|
+
const projectId = asRecord(asRecord(asRecord((yield* readAppJson)["expo"])?.["extra"])?.["betterUpdate"])?.["projectId"];
|
|
1620
1624
|
if (typeof projectId !== "string") return yield* new ProjectNotLinkedError({ message: "Project not linked. Run `better-update link` to connect this project, or set expo.extra.betterUpdate.projectId in app.json." });
|
|
1621
1625
|
return projectId;
|
|
1622
1626
|
});
|
|
1623
1627
|
const readSlug = Effect.gen(function* () {
|
|
1624
|
-
const
|
|
1625
|
-
const expo = asRecord(appJson["expo"]);
|
|
1626
|
-
const slug = expo?.["slug"];
|
|
1628
|
+
const slug = asRecord((yield* readAppJson)["expo"])?.["slug"];
|
|
1627
1629
|
if (typeof slug !== "string") return yield* new ProjectNotLinkedError({ message: "Missing expo.slug in app.json. Required to identify the project." });
|
|
1628
1630
|
return slug;
|
|
1629
1631
|
});
|
|
1630
|
-
const writeProjectId = (id
|
|
1632
|
+
const writeProjectId = (id) => Effect.gen(function* () {
|
|
1631
1633
|
const fs = yield* FileSystem.FileSystem;
|
|
1632
1634
|
const appJson = yield* readAppJson;
|
|
1633
1635
|
const expo = asRecord(appJson["expo"]) ?? {};
|
|
1634
1636
|
const extra = asRecord(expo["extra"]) ?? {};
|
|
1635
1637
|
const betterUpdate = asRecord(extra["betterUpdate"]) ?? {};
|
|
1636
|
-
betterUpdate["projectId"] = id
|
|
1638
|
+
betterUpdate["projectId"] = id;
|
|
1637
1639
|
extra["betterUpdate"] = betterUpdate;
|
|
1638
1640
|
expo["extra"] = extra;
|
|
1639
1641
|
appJson["expo"] = expo;
|
|
@@ -1657,9 +1659,8 @@ const printKeyValue = (pairs) => Effect.gen(function* () {
|
|
|
1657
1659
|
|
|
1658
1660
|
//#endregion
|
|
1659
1661
|
//#region src/application/command-exit.ts
|
|
1660
|
-
const exitWith = (code, message
|
|
1661
|
-
|
|
1662
|
-
yield* runtime.setExitCode(code);
|
|
1662
|
+
const exitWith = (code, message) => Console.error(message).pipe(Effect.zipRight(Effect.gen(function* () {
|
|
1663
|
+
yield* (yield* CliRuntime).setExitCode(code);
|
|
1663
1664
|
})));
|
|
1664
1665
|
|
|
1665
1666
|
//#endregion
|
|
@@ -1692,12 +1693,7 @@ const makeCommandErrorHandler = (extras = {}) => {
|
|
|
1692
1693
|
handlers[tag] = (error) => exitWith(resolvedCode, systemFormat ? systemFormat(error) : error.message);
|
|
1693
1694
|
}
|
|
1694
1695
|
return (effect) => {
|
|
1695
|
-
|
|
1696
|
-
// eslint-disable-next-line typescript/no-unsafe-type-assertion -- Effect.catchTags tag-inference requires a literal object; we accept a dynamic handler map so tags are chosen at runtime
|
|
1697
|
-
Effect.catchTags(handlers),
|
|
1698
|
-
Effect.catchAll((cause) => exitWith(1, formatCause(cause)))
|
|
1699
|
-
);
|
|
1700
|
-
return piped;
|
|
1696
|
+
return effect.pipe(Effect.catchTags(handlers), Effect.catchAll((cause) => exitWith(1, formatCause(cause))));
|
|
1701
1697
|
};
|
|
1702
1698
|
};
|
|
1703
1699
|
|
|
@@ -1802,10 +1798,10 @@ const platformsCommand = Command.make("platforms", { period: period$1 }, (opts)
|
|
|
1802
1798
|
"Platform",
|
|
1803
1799
|
"Requests",
|
|
1804
1800
|
"Devices"
|
|
1805
|
-
], result.platforms.map((platform
|
|
1806
|
-
platform
|
|
1807
|
-
String(platform
|
|
1808
|
-
String(platform
|
|
1801
|
+
], result.platforms.map((platform) => [
|
|
1802
|
+
platform.platform,
|
|
1803
|
+
String(platform.requests),
|
|
1804
|
+
String(platform.devices)
|
|
1809
1805
|
]));
|
|
1810
1806
|
}).pipe(handleAnalyticsCommandErrors));
|
|
1811
1807
|
|
|
@@ -1932,8 +1928,7 @@ const idArg$1 = Args.text({ name: "id" });
|
|
|
1932
1928
|
const nameOption$1 = Options.text("name");
|
|
1933
1929
|
const listCommand$6 = Command.make("list", {}, () => Effect.gen(function* () {
|
|
1934
1930
|
const projectId = yield* readProjectId;
|
|
1935
|
-
const
|
|
1936
|
-
const { items } = yield* api.branches.list({ urlParams: {
|
|
1931
|
+
const { items } = yield* (yield* apiClient).branches.list({ urlParams: {
|
|
1937
1932
|
projectId,
|
|
1938
1933
|
page: 1,
|
|
1939
1934
|
limit: 1e3
|
|
@@ -1946,39 +1941,36 @@ const listCommand$6 = Command.make("list", {}, () => Effect.gen(function* () {
|
|
|
1946
1941
|
"ID",
|
|
1947
1942
|
"Name",
|
|
1948
1943
|
"Created"
|
|
1949
|
-
], items.map((branch
|
|
1950
|
-
branch
|
|
1951
|
-
branch
|
|
1952
|
-
branch
|
|
1944
|
+
], items.map((branch) => [
|
|
1945
|
+
branch.id,
|
|
1946
|
+
branch.name,
|
|
1947
|
+
branch.createdAt
|
|
1953
1948
|
]));
|
|
1954
1949
|
}).pipe(handleErrors$1));
|
|
1955
1950
|
const createCommand$3 = Command.make("create", { name: nameOption$1 }, (opts) => Effect.gen(function* () {
|
|
1956
1951
|
const projectId = yield* readProjectId;
|
|
1957
|
-
const
|
|
1958
|
-
const branch$6 = yield* api.branches.create({ payload: {
|
|
1952
|
+
const branch = yield* (yield* apiClient).branches.create({ payload: {
|
|
1959
1953
|
projectId,
|
|
1960
1954
|
name: opts.name
|
|
1961
1955
|
} });
|
|
1962
1956
|
yield* printKeyValue([
|
|
1963
|
-
["ID", branch
|
|
1964
|
-
["Name", branch
|
|
1965
|
-
["Created", branch
|
|
1957
|
+
["ID", branch.id],
|
|
1958
|
+
["Name", branch.name],
|
|
1959
|
+
["Created", branch.createdAt]
|
|
1966
1960
|
]);
|
|
1967
1961
|
}).pipe(handleErrors$1));
|
|
1968
1962
|
const renameCommand$1 = Command.make("rename", {
|
|
1969
1963
|
id: idArg$1,
|
|
1970
1964
|
name: nameOption$1
|
|
1971
1965
|
}, (opts) => Effect.gen(function* () {
|
|
1972
|
-
const
|
|
1973
|
-
const branch$6 = yield* api.branches.rename({
|
|
1966
|
+
const branch = yield* (yield* apiClient).branches.rename({
|
|
1974
1967
|
path: { id: opts.id },
|
|
1975
1968
|
payload: { name: opts.name }
|
|
1976
1969
|
});
|
|
1977
|
-
yield* Console.log(`Branch renamed to "${branch
|
|
1970
|
+
yield* Console.log(`Branch renamed to "${branch.name}".`);
|
|
1978
1971
|
}).pipe(handleErrors$1));
|
|
1979
1972
|
const deleteCommand$6 = Command.make("delete", { id: idArg$1 }, (opts) => Effect.gen(function* () {
|
|
1980
|
-
|
|
1981
|
-
yield* api.branches.delete({ path: { id: opts.id } });
|
|
1973
|
+
yield* (yield* apiClient).branches.delete({ path: { id: opts.id } });
|
|
1982
1974
|
yield* Console.log(`Branch ${opts.id} deleted.`);
|
|
1983
1975
|
}).pipe(handleErrors$1));
|
|
1984
1976
|
const branchesCommand = Command.make("branches", {}, () => Console.log("Manage branches. Run with --help for subcommands.")).pipe(Command.withSubcommands([
|
|
@@ -2002,7 +1994,7 @@ const escapeGroovySingleQuoted = (value) => value.replaceAll("\\", String.raw`\\
|
|
|
2002
1994
|
* `./gradlew --init-script <path>` so the keystore never has to live in the
|
|
2003
1995
|
* project tree.
|
|
2004
1996
|
*/
|
|
2005
|
-
const renderSigningGradle = ({ keystorePath, storePassword, keyAlias
|
|
1997
|
+
const renderSigningGradle = ({ keystorePath, storePassword, keyAlias, keyPassword }) => `allprojects {
|
|
2006
1998
|
afterEvaluate { project ->
|
|
2007
1999
|
if (project.plugins.hasPlugin('com.android.application')) {
|
|
2008
2000
|
project.android {
|
|
@@ -2010,8 +2002,8 @@ const renderSigningGradle = ({ keystorePath, storePassword, keyAlias: keyAlias$1
|
|
|
2010
2002
|
release {
|
|
2011
2003
|
storeFile file('${escapeGroovySingleQuoted(keystorePath)}')
|
|
2012
2004
|
storePassword '${escapeGroovySingleQuoted(storePassword)}'
|
|
2013
|
-
keyAlias '${escapeGroovySingleQuoted(keyAlias
|
|
2014
|
-
keyPassword '${escapeGroovySingleQuoted(keyPassword
|
|
2005
|
+
keyAlias '${escapeGroovySingleQuoted(keyAlias)}'
|
|
2006
|
+
keyPassword '${escapeGroovySingleQuoted(keyPassword)}'
|
|
2015
2007
|
}
|
|
2016
2008
|
}
|
|
2017
2009
|
buildTypes {
|
|
@@ -2058,12 +2050,10 @@ const walkAndFind = (root, extension) => Effect.gen(function* () {
|
|
|
2058
2050
|
return results;
|
|
2059
2051
|
});
|
|
2060
2052
|
const newest = (files, minMtimeMs) => {
|
|
2061
|
-
|
|
2062
|
-
return maxBy(eligible, (file$1) => file$1.mtimeMs);
|
|
2053
|
+
return maxBy(minMtimeMs === void 0 ? files : files.filter((file) => file.mtimeMs >= minMtimeMs), (file) => file.mtimeMs);
|
|
2063
2054
|
};
|
|
2064
2055
|
const findIosArtifact = ({ exportPath }) => Effect.gen(function* () {
|
|
2065
|
-
const
|
|
2066
|
-
const picked = newest(files);
|
|
2056
|
+
const picked = newest(yield* walkAndFind(exportPath, ".ipa"));
|
|
2067
2057
|
if (!picked) return yield* new ArtifactNotFoundError({ message: `No .ipa file found under "${exportPath}".` });
|
|
2068
2058
|
return picked.path;
|
|
2069
2059
|
});
|
|
@@ -2071,12 +2061,9 @@ const findAndroidArtifact = ({ projectRoot, format, flavor, buildType, minMtimeM
|
|
|
2071
2061
|
const outputsRoot = path.join(projectRoot, "android", "app", "build", "outputs");
|
|
2072
2062
|
const subdir = format === "aab" ? "bundle" : "apk";
|
|
2073
2063
|
const variantDir = flavor ? `${flavor}${capitalize(buildType)}` : buildType;
|
|
2074
|
-
const
|
|
2075
|
-
const direct = yield* walkAndFind(expectedDir, `.${format}`);
|
|
2076
|
-
const pickedDirect = newest(direct, minMtimeMs);
|
|
2064
|
+
const pickedDirect = newest(yield* walkAndFind(path.join(outputsRoot, subdir, variantDir), `.${format}`), minMtimeMs);
|
|
2077
2065
|
if (pickedDirect) return pickedDirect.path;
|
|
2078
|
-
const
|
|
2079
|
-
const pickedFallback = newest(fallback, minMtimeMs);
|
|
2066
|
+
const pickedFallback = newest(yield* walkAndFind(outputsRoot, `.${format}`), minMtimeMs);
|
|
2080
2067
|
if (!pickedFallback) return yield* new ArtifactNotFoundError({ message: `No .${format} artifact found under "${outputsRoot}"${minMtimeMs === void 0 ? "" : " (newer than build start)"}.` });
|
|
2081
2068
|
return pickedFallback.path;
|
|
2082
2069
|
});
|
|
@@ -2105,25 +2092,25 @@ const bindHint = "Bind the bundle via the dashboard (Credentials → iOS Bundle
|
|
|
2105
2092
|
const permissionHint = "Ask an org admin to grant the build-credentials download permission.";
|
|
2106
2093
|
const androidBindHint = "Register the package in the dashboard (Credentials → Android Build Credentials) and bind a keystore to the default group.";
|
|
2107
2094
|
const hasTag = (cause) => typeof cause === "object" && cause !== null && "_tag" in cause;
|
|
2108
|
-
const resolveErrorToMissingCredentials = (cause, platform
|
|
2095
|
+
const resolveErrorToMissingCredentials = (cause, platform) => {
|
|
2109
2096
|
const tag = hasTag(cause) ? cause._tag : null;
|
|
2110
|
-
const message
|
|
2111
|
-
const platformLabel = platform
|
|
2112
|
-
const bind = platform
|
|
2097
|
+
const message = hasTag(cause) && typeof cause.message === "string" ? cause.message : null;
|
|
2098
|
+
const platformLabel = platform === "ios" ? "iOS" : "Android";
|
|
2099
|
+
const bind = platform === "ios" ? bindHint : androidBindHint;
|
|
2113
2100
|
if (tag === "Forbidden") return new MissingCredentialsError({
|
|
2114
|
-
message: message
|
|
2101
|
+
message: message ?? `Permission denied when resolving ${platformLabel} build credentials`,
|
|
2115
2102
|
hint: permissionHint
|
|
2116
2103
|
});
|
|
2117
2104
|
if (tag === "NotFound") return new MissingCredentialsError({
|
|
2118
|
-
message: message
|
|
2105
|
+
message: message ?? `No ${platformLabel} build credentials configured`,
|
|
2119
2106
|
hint: bind
|
|
2120
2107
|
});
|
|
2121
2108
|
if (tag === "BadRequest") return new MissingCredentialsError({
|
|
2122
|
-
message: message
|
|
2109
|
+
message: message ?? `${platformLabel} build credentials are misconfigured`,
|
|
2123
2110
|
hint: bind
|
|
2124
2111
|
});
|
|
2125
2112
|
return new MissingCredentialsError({
|
|
2126
|
-
message: message
|
|
2113
|
+
message: message ?? `Failed to resolve ${platformLabel} build credentials`,
|
|
2127
2114
|
hint: bind
|
|
2128
2115
|
});
|
|
2129
2116
|
};
|
|
@@ -2179,26 +2166,26 @@ const downloadAndroidCredentials = (api, options) => Effect.gen(function* () {
|
|
|
2179
2166
|
|
|
2180
2167
|
//#endregion
|
|
2181
2168
|
//#region src/lib/sha256.ts
|
|
2182
|
-
const hashReadError = (message
|
|
2169
|
+
const hashReadError = (message) => new BuildFailedError({
|
|
2183
2170
|
step: "sha256",
|
|
2184
2171
|
exitCode: 1,
|
|
2185
|
-
message
|
|
2172
|
+
message
|
|
2186
2173
|
});
|
|
2187
|
-
const hashFile = (path
|
|
2188
|
-
const hash
|
|
2189
|
-
const stream = createReadStream(path
|
|
2174
|
+
const hashFile = (path, formatDigest) => Effect.async((resume) => {
|
|
2175
|
+
const hash = createHash("sha256");
|
|
2176
|
+
const stream = createReadStream(path);
|
|
2190
2177
|
let byteSize = 0;
|
|
2191
2178
|
stream.on("data", (chunk) => {
|
|
2192
2179
|
const buffer = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
|
|
2193
2180
|
byteSize += buffer.byteLength;
|
|
2194
|
-
hash
|
|
2181
|
+
hash.update(buffer);
|
|
2195
2182
|
});
|
|
2196
2183
|
stream.on("error", (error) => {
|
|
2197
2184
|
resume(Effect.fail(hashReadError(`Failed to read file for SHA-256: ${error.message}`)));
|
|
2198
2185
|
});
|
|
2199
2186
|
stream.on("end", () => {
|
|
2200
2187
|
resume(Effect.succeed({
|
|
2201
|
-
digest: formatDigest(hash
|
|
2188
|
+
digest: formatDigest(hash.digest()),
|
|
2202
2189
|
byteSize
|
|
2203
2190
|
}));
|
|
2204
2191
|
});
|
|
@@ -2208,7 +2195,7 @@ const hashFile = (path$1, formatDigest) => Effect.async((resume) => {
|
|
|
2208
2195
|
* hash API. The file is never fully loaded into memory — chunks flow through
|
|
2209
2196
|
* `createReadStream` into `crypto.createHash("sha256")`.
|
|
2210
2197
|
*/
|
|
2211
|
-
const sha256File = (path
|
|
2198
|
+
const sha256File = (path) => hashFile(path, (digest) => digest.toString("hex")).pipe(Effect.map(({ digest, byteSize }) => ({
|
|
2212
2199
|
sha256: digest,
|
|
2213
2200
|
byteSize
|
|
2214
2201
|
})));
|
|
@@ -2275,7 +2262,6 @@ const runStepFormatted = (cmd, step, formatter) => Effect.gen(function* () {
|
|
|
2275
2262
|
message: `${step} exited with code ${code}`
|
|
2276
2263
|
});
|
|
2277
2264
|
}
|
|
2278
|
-
return void 0;
|
|
2279
2265
|
});
|
|
2280
2266
|
|
|
2281
2267
|
//#endregion
|
|
@@ -2374,8 +2360,7 @@ const renderExportOptionsPlist = ({ method, teamId, bundleId, provisioningProfil
|
|
|
2374
2360
|
//#region src/lib/ios-keychain.ts
|
|
2375
2361
|
const runOrFail = (cmd, step) => Command$1.string(cmd).pipe(Effect.mapError((cause) => new KeychainError({ message: `keychain ${step} failed: ${String(cause)}` })));
|
|
2376
2362
|
const listCurrentKeychains = Effect.gen(function* () {
|
|
2377
|
-
|
|
2378
|
-
return output.split("\n").map((line) => line.trim().replace(/^"/, "").replace(/"$/, "")).filter((line) => line.length > 0);
|
|
2363
|
+
return (yield* runOrFail(Command$1.make("security", "list-keychains", "-d", "user"), "list-keychains")).split("\n").map((line) => line.trim().replace(/^"/, "").replace(/"$/, "")).filter((line) => line.length > 0);
|
|
2379
2364
|
});
|
|
2380
2365
|
const parseSigningIdentity = (output) => {
|
|
2381
2366
|
const lines = output.split("\n");
|
|
@@ -2383,7 +2368,6 @@ const parseSigningIdentity = (output) => {
|
|
|
2383
2368
|
const match = /"([^"]+)"/.exec(line);
|
|
2384
2369
|
if (match?.[1]) return match[1];
|
|
2385
2370
|
}
|
|
2386
|
-
return void 0;
|
|
2387
2371
|
};
|
|
2388
2372
|
/**
|
|
2389
2373
|
* Acquire an ephemeral macOS keychain, import a `.p12` into it, add it to the
|
|
@@ -2395,34 +2379,28 @@ const acquireKeychain = ({ tempDir, p12Path, p12Password }) => {
|
|
|
2395
2379
|
const keychainName = `better-update-${randomUUID()}.keychain-db`;
|
|
2396
2380
|
const keychainPath = path.join(tempDir, keychainName);
|
|
2397
2381
|
const keychainPassword = randomBytes(32).toString("hex");
|
|
2398
|
-
return Effect.acquireRelease(
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
// ── release ─────────────────────────────────────────────────
|
|
2421
|
-
({ priorKeychains }) => Effect.gen(function* () {
|
|
2422
|
-
yield* Command$1.string(Command$1.make("security", "list-keychains", "-d", "user", "-s", ...priorKeychains)).pipe(Effect.catchAll(() => Effect.void));
|
|
2423
|
-
yield* Command$1.string(Command$1.make("security", "delete-keychain", keychainPath)).pipe(Effect.catchAll(() => Effect.void));
|
|
2424
|
-
})
|
|
2425
|
-
).pipe(Effect.map(({ handle }) => handle));
|
|
2382
|
+
return Effect.acquireRelease(Effect.gen(function* () {
|
|
2383
|
+
const priorKeychains = yield* listCurrentKeychains;
|
|
2384
|
+
yield* runOrFail(Command$1.make("security", "create-keychain", "-p", keychainPassword, keychainPath), "create-keychain");
|
|
2385
|
+
yield* runOrFail(Command$1.make("security", "unlock-keychain", "-p", keychainPassword, keychainPath), "unlock-keychain");
|
|
2386
|
+
yield* runOrFail(Command$1.make("security", "set-keychain-settings", "-t", "3600", "-l", keychainPath), "set-keychain-settings");
|
|
2387
|
+
yield* runOrFail(Command$1.make("security", "import", p12Path, "-k", keychainPath, "-P", p12Password, "-T", "/usr/bin/codesign"), "import");
|
|
2388
|
+
yield* runOrFail(Command$1.make("security", "set-key-partition-list", "-S", "apple-tool:,apple:,codesign:", "-s", "-k", keychainPassword, keychainPath), "set-key-partition-list");
|
|
2389
|
+
yield* runOrFail(Command$1.make("security", "list-keychains", "-d", "user", "-s", keychainPath, ...priorKeychains), "list-keychains -s (add)");
|
|
2390
|
+
const signingIdentity = parseSigningIdentity(yield* runOrFail(Command$1.make("security", "find-identity", "-v", "-p", "codesigning", keychainPath), "find-identity"));
|
|
2391
|
+
if (!signingIdentity) return yield* new KeychainError({ message: "No code signing identity found after importing .p12 into ephemeral keychain." });
|
|
2392
|
+
return {
|
|
2393
|
+
handle: {
|
|
2394
|
+
keychainName,
|
|
2395
|
+
keychainPath,
|
|
2396
|
+
signingIdentity
|
|
2397
|
+
},
|
|
2398
|
+
priorKeychains
|
|
2399
|
+
};
|
|
2400
|
+
}), ({ priorKeychains }) => Effect.gen(function* () {
|
|
2401
|
+
yield* Command$1.string(Command$1.make("security", "list-keychains", "-d", "user", "-s", ...priorKeychains)).pipe(Effect.catchAll(() => Effect.void));
|
|
2402
|
+
yield* Command$1.string(Command$1.make("security", "delete-keychain", keychainPath)).pipe(Effect.catchAll(() => Effect.void));
|
|
2403
|
+
})).pipe(Effect.map(({ handle }) => handle));
|
|
2426
2404
|
};
|
|
2427
2405
|
|
|
2428
2406
|
//#endregion
|
|
@@ -2437,8 +2415,7 @@ const parsePlistXml = (xml) => plist.parse(xml);
|
|
|
2437
2415
|
* Uses bplist-parser for Apple's binary plist format.
|
|
2438
2416
|
*/
|
|
2439
2417
|
const parsePlistBinary = (buffer) => {
|
|
2440
|
-
const
|
|
2441
|
-
const [result] = bplistParser.parseBuffer(buffer);
|
|
2418
|
+
const [result] = __require("bplist-parser").parseBuffer(buffer);
|
|
2442
2419
|
return result;
|
|
2443
2420
|
};
|
|
2444
2421
|
const BPLIST_MAGIC = Buffer.from("bplist00");
|
|
@@ -2456,7 +2433,6 @@ const getString = (obj, key) => {
|
|
|
2456
2433
|
const getFirstArrayString = (obj, key) => {
|
|
2457
2434
|
const value = obj[key];
|
|
2458
2435
|
if (Array.isArray(value) && typeof value[0] === "string") return value[0];
|
|
2459
|
-
return void 0;
|
|
2460
2436
|
};
|
|
2461
2437
|
/**
|
|
2462
2438
|
* Extract `UUID`, `Name`, and the first `TeamIdentifier` from the XML plist
|
|
@@ -2469,12 +2445,12 @@ const extractProvisioningInfo = (plistXml) => Effect.gen(function* () {
|
|
|
2469
2445
|
catch: (error) => new ProvisioningError({ message: `Failed to parse provisioning profile plist: ${error instanceof Error ? error.message : String(error)}` })
|
|
2470
2446
|
});
|
|
2471
2447
|
const uuid = getString(parsed, "UUID");
|
|
2472
|
-
const name
|
|
2448
|
+
const name = getString(parsed, "Name");
|
|
2473
2449
|
const teamId = getFirstArrayString(parsed, "TeamIdentifier");
|
|
2474
|
-
if (!uuid || !name
|
|
2450
|
+
if (!uuid || !name || !teamId) return yield* new ProvisioningError({ message: `Failed to parse provisioning profile: missing ${uuid ? "" : "UUID "}${name ? "" : "Name "}${teamId ? "" : "TeamIdentifier "}`.trim() });
|
|
2475
2451
|
return {
|
|
2476
2452
|
uuid,
|
|
2477
|
-
name
|
|
2453
|
+
name,
|
|
2478
2454
|
teamId
|
|
2479
2455
|
};
|
|
2480
2456
|
});
|
|
@@ -2488,13 +2464,11 @@ const userProvisioningProfilesDir = () => path.join(os.homedir(), "Library", "Mo
|
|
|
2488
2464
|
*/
|
|
2489
2465
|
const installProvisioningProfile = ({ profilePath }) => Effect.acquireRelease(Effect.gen(function* () {
|
|
2490
2466
|
const fs = yield* FileSystem.FileSystem;
|
|
2491
|
-
const
|
|
2492
|
-
const info = yield* extractProvisioningInfo(plistXml);
|
|
2467
|
+
const info = yield* extractProvisioningInfo(yield* Command$1.string(Command$1.make("security", "cms", "-D", "-i", profilePath)).pipe(Effect.mapError((cause) => new ProvisioningError({ message: `security cms -D failed for ${profilePath}: ${String(cause)}` }))));
|
|
2493
2468
|
const targetDir = userProvisioningProfilesDir();
|
|
2494
2469
|
const installedPath = path.join(targetDir, `${info.uuid}.mobileprovision`);
|
|
2495
2470
|
yield* fs.makeDirectory(targetDir, { recursive: true }).pipe(Effect.catchAll((cause) => new ProvisioningError({ message: `Failed to create provisioning profiles dir: ${String(cause)}` })));
|
|
2496
|
-
|
|
2497
|
-
if (alreadyInstalled) return {
|
|
2471
|
+
if (yield* fs.exists(installedPath).pipe(Effect.orElseSucceed(() => false))) return {
|
|
2498
2472
|
...info,
|
|
2499
2473
|
installedPath,
|
|
2500
2474
|
ownsInstallation: false
|
|
@@ -2507,11 +2481,10 @@ const installProvisioningProfile = ({ profilePath }) => Effect.acquireRelease(Ef
|
|
|
2507
2481
|
};
|
|
2508
2482
|
}), (acquired) => Effect.gen(function* () {
|
|
2509
2483
|
if (!acquired.ownsInstallation) return;
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
})).pipe(Effect.map(({ uuid, name: name$2, teamId, installedPath }) => ({
|
|
2484
|
+
yield* (yield* FileSystem.FileSystem).remove(acquired.installedPath).pipe(Effect.catchAll(() => Effect.void));
|
|
2485
|
+
})).pipe(Effect.map(({ uuid, name, teamId, installedPath }) => ({
|
|
2513
2486
|
uuid,
|
|
2514
|
-
name
|
|
2487
|
+
name,
|
|
2515
2488
|
teamId,
|
|
2516
2489
|
installedPath
|
|
2517
2490
|
})));
|
|
@@ -2528,13 +2501,10 @@ const installProvisioningProfile = ({ profilePath }) => Effect.acquireRelease(Ef
|
|
|
2528
2501
|
*/
|
|
2529
2502
|
const validateIosBuild = (params) => Effect.gen(function* () {
|
|
2530
2503
|
const appDir = yield* findAppDirectory(params.archivePath).pipe(Effect.catchAll(() => Effect.succeed(void 0)));
|
|
2531
|
-
if (!appDir) {
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
warnings: warnings$1
|
|
2536
|
-
};
|
|
2537
|
-
}
|
|
2504
|
+
if (!appDir) return {
|
|
2505
|
+
passed: false,
|
|
2506
|
+
warnings: ["Could not locate .app bundle in archive — skipping post-build validation"]
|
|
2507
|
+
};
|
|
2538
2508
|
const bundleWarning = yield* checkBundleId(appDir, params.expectedBundleId).pipe(Effect.catchAll(() => Effect.succeed(void 0)));
|
|
2539
2509
|
const profileWarnings = yield* checkEmbeddedProfile(appDir, params.expectedProfileUuid, params.expectedTeamId).pipe(Effect.catchAll(() => Effect.succeed([])));
|
|
2540
2510
|
const warnings = [...bundleWarning ? [bundleWarning] : [], ...profileWarnings];
|
|
@@ -2550,8 +2520,7 @@ const validateIosBuild = (params) => Effect.gen(function* () {
|
|
|
2550
2520
|
const findAppDirectory = (archivePath) => Effect.gen(function* () {
|
|
2551
2521
|
const fs = yield* FileSystem.FileSystem;
|
|
2552
2522
|
const productsDir = path.join(archivePath, "Products", "Applications");
|
|
2553
|
-
const
|
|
2554
|
-
const appEntry = entries.find((entry) => entry.endsWith(".app"));
|
|
2523
|
+
const appEntry = (yield* fs.readDirectory(productsDir)).find((entry) => entry.endsWith(".app"));
|
|
2555
2524
|
if (!appEntry) return yield* Effect.fail("No .app found");
|
|
2556
2525
|
return path.join(productsDir, appEntry);
|
|
2557
2526
|
});
|
|
@@ -2559,16 +2528,13 @@ const checkBundleId = (appDir, expectedBundleId) => Effect.gen(function* () {
|
|
|
2559
2528
|
const fs = yield* FileSystem.FileSystem;
|
|
2560
2529
|
const plistPath = path.join(appDir, "Info.plist");
|
|
2561
2530
|
const data = yield* fs.readFile(plistPath);
|
|
2562
|
-
const
|
|
2563
|
-
const actualBundleId = parsed["CFBundleIdentifier"];
|
|
2531
|
+
const actualBundleId = parsePlist(Buffer.from(data))["CFBundleIdentifier"];
|
|
2564
2532
|
if (typeof actualBundleId === "string" && actualBundleId !== expectedBundleId) return `Bundle ID mismatch: expected "${expectedBundleId}", got "${actualBundleId}"`;
|
|
2565
|
-
return void 0;
|
|
2566
2533
|
});
|
|
2567
2534
|
const checkEmbeddedProfile = (appDir, expectedUuid, expectedTeamId) => Effect.gen(function* () {
|
|
2568
2535
|
const warnings = [];
|
|
2569
2536
|
const profilePath = path.join(appDir, "embedded.mobileprovision");
|
|
2570
|
-
const
|
|
2571
|
-
const parsed = parsePlistXml(plistXml);
|
|
2537
|
+
const parsed = parsePlistXml(yield* Command$1.string(Command$1.make("security", "cms", "-D", "-i", profilePath)));
|
|
2572
2538
|
const actualUuid = parsed["UUID"];
|
|
2573
2539
|
if (typeof actualUuid === "string" && actualUuid !== expectedUuid) warnings.push(`Profile UUID mismatch: expected "${expectedUuid}", got "${actualUuid}"`);
|
|
2574
2540
|
const teamIdentifiers = parsed["TeamIdentifier"];
|
|
@@ -2599,9 +2565,7 @@ const createXcodebuildFormatter = (projectRoot) => {
|
|
|
2599
2565
|
//#endregion
|
|
2600
2566
|
//#region src/commands/build/ios.ts
|
|
2601
2567
|
const findXcworkspace = (iosDir) => Effect.gen(function* () {
|
|
2602
|
-
const
|
|
2603
|
-
const entries = yield* fs.readDirectory(iosDir);
|
|
2604
|
-
const workspace = entries.find((entry) => entry.endsWith(".xcworkspace"));
|
|
2568
|
+
const workspace = (yield* (yield* FileSystem.FileSystem).readDirectory(iosDir)).find((entry) => entry.endsWith(".xcworkspace"));
|
|
2605
2569
|
if (!workspace) return yield* new BuildFailedError({
|
|
2606
2570
|
step: "detect xcworkspace",
|
|
2607
2571
|
exitCode: 1,
|
|
@@ -2735,9 +2699,7 @@ const reserveAndUpload = (api, input) => Effect.gen(function* () {
|
|
|
2735
2699
|
//#region src/lib/build-profile.ts
|
|
2736
2700
|
const asString$1 = (value) => typeof value === "string" ? value : void 0;
|
|
2737
2701
|
const getBetterUpdateExtra = (appJson) => {
|
|
2738
|
-
|
|
2739
|
-
const extra = asRecord(expo?.["extra"]);
|
|
2740
|
-
return asRecord(extra?.["betterUpdate"]);
|
|
2702
|
+
return asRecord(asRecord(asRecord(appJson["expo"])?.["extra"])?.["betterUpdate"]);
|
|
2741
2703
|
};
|
|
2742
2704
|
const VALID_IOS_DISTRIBUTIONS = [
|
|
2743
2705
|
"app-store",
|
|
@@ -2748,10 +2710,10 @@ const VALID_IOS_DISTRIBUTIONS = [
|
|
|
2748
2710
|
const isIosDistribution = (value) => VALID_IOS_DISTRIBUTIONS.includes(value);
|
|
2749
2711
|
const readIosProfile = (raw) => {
|
|
2750
2712
|
const iosRaw = asRecord(raw);
|
|
2751
|
-
if (!iosRaw) return
|
|
2713
|
+
if (!iosRaw) return;
|
|
2752
2714
|
const distributionRaw = asString$1(iosRaw["distribution"]);
|
|
2753
|
-
if (!distributionRaw) return
|
|
2754
|
-
if (!isIosDistribution(distributionRaw)) return
|
|
2715
|
+
if (!distributionRaw) return;
|
|
2716
|
+
if (!isIosDistribution(distributionRaw)) return;
|
|
2755
2717
|
const distribution = distributionRaw;
|
|
2756
2718
|
const buildConfiguration = asString$1(iosRaw["buildConfiguration"]);
|
|
2757
2719
|
const scheme = asString$1(iosRaw["scheme"]);
|
|
@@ -2767,33 +2729,31 @@ const resolveAndroidDistribution = (raw, format) => {
|
|
|
2767
2729
|
};
|
|
2768
2730
|
const readAndroidProfile = (raw) => {
|
|
2769
2731
|
const androidRaw = asRecord(raw);
|
|
2770
|
-
if (!androidRaw) return
|
|
2732
|
+
if (!androidRaw) return;
|
|
2771
2733
|
const formatValue = asString$1(androidRaw["format"]);
|
|
2772
2734
|
const format = formatValue === "apk" || formatValue === "aab" ? formatValue : void 0;
|
|
2773
|
-
if (!format) return
|
|
2735
|
+
if (!format) return;
|
|
2774
2736
|
const buildTypeValue = asString$1(androidRaw["buildType"]);
|
|
2775
2737
|
const buildType = buildTypeValue === "debug" || buildTypeValue === "release" ? buildTypeValue : void 0;
|
|
2776
2738
|
const flavor = asString$1(androidRaw["flavor"]);
|
|
2777
|
-
const distribution = resolveAndroidDistribution(asString$1(androidRaw["distribution"]), format);
|
|
2778
2739
|
return {
|
|
2779
2740
|
format,
|
|
2780
|
-
distribution,
|
|
2741
|
+
distribution: resolveAndroidDistribution(asString$1(androidRaw["distribution"]), format),
|
|
2781
2742
|
...buildType === void 0 ? {} : { buildType },
|
|
2782
2743
|
...flavor === void 0 ? {} : { flavor }
|
|
2783
2744
|
};
|
|
2784
2745
|
};
|
|
2785
2746
|
const readBuildProfile = (appJson, profileName) => Effect.gen(function* () {
|
|
2786
|
-
const
|
|
2787
|
-
const profiles = asRecord(betterUpdate?.["profiles"]);
|
|
2747
|
+
const profiles = asRecord(getBetterUpdateExtra(appJson)?.["profiles"]);
|
|
2788
2748
|
if (!profiles) return yield* new BuildProfileError({ message: "No build profiles defined. Add expo.extra.betterUpdate.profiles to app.json." });
|
|
2789
2749
|
const profileRaw = asRecord(profiles[profileName]);
|
|
2790
2750
|
if (!profileRaw) return yield* new BuildProfileError({ message: `Build profile "${profileName}" not found in app.json.` });
|
|
2791
|
-
const environment
|
|
2751
|
+
const environment = asString$1(profileRaw["environment"]) ?? "production";
|
|
2792
2752
|
const ios = readIosProfile(profileRaw["ios"]);
|
|
2793
2753
|
const android = readAndroidProfile(profileRaw["android"]);
|
|
2794
2754
|
return {
|
|
2795
2755
|
name: profileName,
|
|
2796
|
-
environment
|
|
2756
|
+
environment,
|
|
2797
2757
|
...ios === void 0 ? {} : { ios },
|
|
2798
2758
|
...android === void 0 ? {} : { android }
|
|
2799
2759
|
};
|
|
@@ -2806,19 +2766,15 @@ const readRuntimeVersionMeta = (appJson) => Effect.gen(function* () {
|
|
|
2806
2766
|
rawRuntimeVersion: readRawRuntimeVersion(expo["runtimeVersion"])
|
|
2807
2767
|
};
|
|
2808
2768
|
});
|
|
2809
|
-
const readAppMeta = (appJson, platform
|
|
2769
|
+
const readAppMeta = (appJson, platform) => Effect.gen(function* () {
|
|
2810
2770
|
const expo = asRecord(appJson["expo"]);
|
|
2811
2771
|
if (!expo) return yield* new BuildProfileError({ message: "Missing expo section in app.json." });
|
|
2812
|
-
if (platform
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
} else {
|
|
2816
|
-
const android = asRecord(expo["android"]);
|
|
2817
|
-
if (!android) return yield* new BuildProfileError({ message: "Missing expo.android section in app.json. Required for Android builds (package)." });
|
|
2818
|
-
}
|
|
2772
|
+
if (platform === "ios") {
|
|
2773
|
+
if (!asRecord(expo["ios"])) return yield* new BuildProfileError({ message: "Missing expo.ios section in app.json. Required for iOS builds (bundleIdentifier)." });
|
|
2774
|
+
} else if (!asRecord(expo["android"])) return yield* new BuildProfileError({ message: "Missing expo.android section in app.json. Required for Android builds (package)." });
|
|
2819
2775
|
const iosSection = asRecord(expo["ios"]);
|
|
2820
2776
|
const androidSection = asRecord(expo["android"]);
|
|
2821
|
-
const buildNumber = platform
|
|
2777
|
+
const buildNumber = platform === "ios" ? asString$1(iosSection?.["buildNumber"]) : asStringOrNumber(androidSection?.["versionCode"]);
|
|
2822
2778
|
return {
|
|
2823
2779
|
bundleId: asString$1(iosSection?.["bundleIdentifier"]),
|
|
2824
2780
|
androidPackage: asString$1(androidSection?.["package"]),
|
|
@@ -2830,14 +2786,11 @@ const readAppMeta = (appJson, platform$7) => Effect.gen(function* () {
|
|
|
2830
2786
|
const asStringOrNumber = (value) => {
|
|
2831
2787
|
if (typeof value === "string") return value;
|
|
2832
2788
|
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
2833
|
-
return void 0;
|
|
2834
2789
|
};
|
|
2835
2790
|
const readRawRuntimeVersion = (value) => {
|
|
2836
2791
|
if (typeof value === "string") return value;
|
|
2837
|
-
const
|
|
2838
|
-
const policy = asString$1(record?.["policy"]);
|
|
2792
|
+
const policy = asString$1(asRecord(value)?.["policy"]);
|
|
2839
2793
|
if (policy) return { policy };
|
|
2840
|
-
return void 0;
|
|
2841
2794
|
};
|
|
2842
2795
|
|
|
2843
2796
|
//#endregion
|
|
@@ -2846,10 +2799,10 @@ const readRawRuntimeVersion = (value) => {
|
|
|
2846
2799
|
* Pull environment variables for a project + environment and flatten them into
|
|
2847
2800
|
* a key/value map. Returns an empty map when the project has no variables.
|
|
2848
2801
|
*/
|
|
2849
|
-
const pullEnvVars = (api, { projectId, environment
|
|
2802
|
+
const pullEnvVars = (api, { projectId, environment }) => api["env-vars"].export({ urlParams: {
|
|
2850
2803
|
projectId,
|
|
2851
|
-
environment
|
|
2852
|
-
} }).pipe(Effect.map((result) => Object.fromEntries(result.items.map((item) => [item.key, item.value]))), Effect.mapError((cause) => new EnvExportError({ message: `Failed to export environment variables for "${environment
|
|
2804
|
+
environment
|
|
2805
|
+
} }).pipe(Effect.map((result) => Object.fromEntries(result.items.map((item) => [item.key, item.value]))), Effect.mapError((cause) => new EnvExportError({ message: `Failed to export environment variables for "${environment}": ${String(cause)}` })));
|
|
2853
2806
|
|
|
2854
2807
|
//#endregion
|
|
2855
2808
|
//#region src/lib/expo-config.ts
|
|
@@ -2872,8 +2825,7 @@ const readExpoConfig = (projectRoot, envVars = {}) => Effect.acquireUseRelease(E
|
|
|
2872
2825
|
return previous;
|
|
2873
2826
|
}), () => Effect.try({
|
|
2874
2827
|
try: () => {
|
|
2875
|
-
const
|
|
2876
|
-
const { getConfig } = expoConfigCjs;
|
|
2828
|
+
const { getConfig } = __require("@expo/config");
|
|
2877
2829
|
const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true });
|
|
2878
2830
|
return exp;
|
|
2879
2831
|
},
|
|
@@ -2882,29 +2834,28 @@ const readExpoConfig = (projectRoot, envVars = {}) => Effect.acquireUseRelease(E
|
|
|
2882
2834
|
for (const [key, value] of Object.entries(previous)) if (value === void 0) delete process.env[key];
|
|
2883
2835
|
else process.env[key] = value;
|
|
2884
2836
|
}));
|
|
2885
|
-
const extractBuildNumber = (config, platform
|
|
2886
|
-
if (platform
|
|
2887
|
-
if (config.android?.versionCode === void 0) return
|
|
2837
|
+
const extractBuildNumber = (config, platform) => {
|
|
2838
|
+
if (platform === "ios") return config.ios?.buildNumber;
|
|
2839
|
+
if (config.android?.versionCode === void 0) return;
|
|
2888
2840
|
return String(config.android.versionCode);
|
|
2889
2841
|
};
|
|
2890
2842
|
const extractRawRuntimeVersion = (config) => {
|
|
2891
2843
|
if (typeof config.runtimeVersion === "string") return config.runtimeVersion;
|
|
2892
2844
|
if (typeof config.runtimeVersion === "object") return config.runtimeVersion;
|
|
2893
|
-
return void 0;
|
|
2894
2845
|
};
|
|
2895
2846
|
/**
|
|
2896
2847
|
* Extract AppMeta from a resolved ExpoConfig (from `@expo/config`).
|
|
2897
2848
|
* Mirrors `readAppMeta` from build-profile.ts but uses the resolved config
|
|
2898
2849
|
* which handles dynamic configs (`app.config.js`, `app.config.ts`).
|
|
2899
2850
|
*/
|
|
2900
|
-
const readAppMetaFromConfig = (config, platform
|
|
2901
|
-
if (platform
|
|
2902
|
-
if (platform
|
|
2851
|
+
const readAppMetaFromConfig = (config, platform) => Effect.gen(function* () {
|
|
2852
|
+
if (platform === "ios" && !config.ios) return yield* new BuildProfileError({ message: "Missing expo.ios section in config. Required for iOS builds (bundleIdentifier)." });
|
|
2853
|
+
if (platform === "android" && !config.android) return yield* new BuildProfileError({ message: "Missing expo.android section in config. Required for Android builds (package)." });
|
|
2903
2854
|
return {
|
|
2904
2855
|
bundleId: config.ios?.bundleIdentifier,
|
|
2905
2856
|
androidPackage: config.android?.package,
|
|
2906
2857
|
appVersion: config.version,
|
|
2907
|
-
buildNumber: extractBuildNumber(config, platform
|
|
2858
|
+
buildNumber: extractBuildNumber(config, platform),
|
|
2908
2859
|
rawRuntimeVersion: extractRawRuntimeVersion(config)
|
|
2909
2860
|
};
|
|
2910
2861
|
});
|
|
@@ -2950,14 +2901,13 @@ const readGradleConfig = (androidDir) => Effect.gen(function* () {
|
|
|
2950
2901
|
const ktsPath = path.join(androidDir, "app", "build.gradle.kts");
|
|
2951
2902
|
const hasGroovy = yield* fs.exists(gradlePath).pipe(Effect.orElseSucceed(() => false));
|
|
2952
2903
|
const hasKts = yield* fs.exists(ktsPath).pipe(Effect.orElseSucceed(() => false));
|
|
2953
|
-
if (!hasGroovy && hasKts) return
|
|
2954
|
-
if (!hasGroovy) return
|
|
2904
|
+
if (!hasGroovy && hasKts) return;
|
|
2905
|
+
if (!hasGroovy) return;
|
|
2955
2906
|
const content = yield* fs.readFileString(gradlePath).pipe(Effect.catchAll(() => Effect.succeed(void 0)));
|
|
2956
|
-
if (!content) return
|
|
2907
|
+
if (!content) return;
|
|
2957
2908
|
return yield* Effect.tryPromise({
|
|
2958
2909
|
try: async () => {
|
|
2959
|
-
|
|
2960
|
-
return gradle.parseText(stripGroovyComments(content));
|
|
2910
|
+
return __require("gradle-to-js").parseText(stripGroovyComments(content));
|
|
2961
2911
|
},
|
|
2962
2912
|
catch: () => void 0
|
|
2963
2913
|
}).pipe(Effect.map(extractGradleConfig), Effect.catchAll(() => Effect.succeed(void 0)));
|
|
@@ -2978,11 +2928,9 @@ const stripGroovyComments = (text) => text.replaceAll(/\/\/.*$/gmu, "").replaceA
|
|
|
2978
2928
|
const parseVersionCode = (raw) => {
|
|
2979
2929
|
if (typeof raw === "number") return raw;
|
|
2980
2930
|
if (typeof raw === "string") return Number.parseInt(raw, 10) || void 0;
|
|
2981
|
-
return void 0;
|
|
2982
2931
|
};
|
|
2983
2932
|
const extractGradleConfig = (parsed) => {
|
|
2984
|
-
const
|
|
2985
|
-
const defaultConfig = asRecord(android?.["defaultConfig"]);
|
|
2933
|
+
const defaultConfig = asRecord(asRecord(parsed["android"])?.["defaultConfig"]);
|
|
2986
2934
|
const applicationId = typeof defaultConfig?.["applicationId"] === "string" ? unquote(defaultConfig["applicationId"]) : void 0;
|
|
2987
2935
|
const versionCode = parseVersionCode(defaultConfig?.["versionCode"]);
|
|
2988
2936
|
const versionName = typeof defaultConfig?.["versionName"] === "string" ? unquote(defaultConfig["versionName"]) : void 0;
|
|
@@ -3005,13 +2953,12 @@ const runFingerprintFull = (projectRoot) => Effect.gen(function* () {
|
|
|
3005
2953
|
catch: () => new FingerprintError({ message: "Failed to parse @expo/fingerprint output as JSON." })
|
|
3006
2954
|
});
|
|
3007
2955
|
if (!isRecord(parsed)) return yield* new FingerprintError({ message: "@expo/fingerprint output was not a JSON object." });
|
|
3008
|
-
const { hash
|
|
3009
|
-
if (typeof hash
|
|
2956
|
+
const { hash } = parsed;
|
|
2957
|
+
if (typeof hash !== "string" || hash.length === 0) return yield* new FingerprintError({ message: "@expo/fingerprint output did not contain a \"hash\" string field." });
|
|
3010
2958
|
const sourcesRaw = parsed["sources"];
|
|
3011
|
-
const sources = Array.isArray(sourcesRaw) ? sourcesRaw : [];
|
|
3012
2959
|
return {
|
|
3013
|
-
hash
|
|
3014
|
-
sources
|
|
2960
|
+
hash,
|
|
2961
|
+
sources: Array.isArray(sourcesRaw) ? sourcesRaw : []
|
|
3015
2962
|
};
|
|
3016
2963
|
});
|
|
3017
2964
|
|
|
@@ -3047,77 +2994,71 @@ const acquireBuildTempDir = Effect.gen(function* () {
|
|
|
3047
2994
|
//#endregion
|
|
3048
2995
|
//#region src/application/build-workflow.ts
|
|
3049
2996
|
const runIosPlatformBuild = (input) => Effect.gen(function* () {
|
|
3050
|
-
const { api, appMeta, envVars, options, profile
|
|
3051
|
-
if (!profile
|
|
3052
|
-
const iosProfile = profile
|
|
2997
|
+
const { api, appMeta, envVars, options, profile, projectId, projectRoot, tempDir } = input;
|
|
2998
|
+
if (!profile.ios) return yield* new BuildProfileError({ message: `Profile "${profile.name}" has no ios section.` });
|
|
2999
|
+
const iosProfile = profile.ios;
|
|
3053
3000
|
const iosBundleId = appMeta.bundleId;
|
|
3054
3001
|
if (!iosBundleId) return yield* new BuildProfileError({ message: "Missing expo.ios.bundleIdentifier in app.json." });
|
|
3055
|
-
const build = yield* runIosBuild({
|
|
3056
|
-
api,
|
|
3057
|
-
tempDir,
|
|
3058
|
-
projectRoot,
|
|
3059
|
-
iosProfile,
|
|
3060
|
-
bundleId: iosBundleId,
|
|
3061
|
-
envVars,
|
|
3062
|
-
projectId,
|
|
3063
|
-
rawOutput: options.rawOutput
|
|
3064
|
-
});
|
|
3065
|
-
const target = {
|
|
3066
|
-
platform: "ios",
|
|
3067
|
-
distribution: iosProfile.distribution,
|
|
3068
|
-
artifactFormat: "ipa"
|
|
3069
|
-
};
|
|
3070
3002
|
return {
|
|
3071
|
-
build
|
|
3072
|
-
|
|
3003
|
+
build: yield* runIosBuild({
|
|
3004
|
+
api,
|
|
3005
|
+
tempDir,
|
|
3006
|
+
projectRoot,
|
|
3007
|
+
iosProfile,
|
|
3008
|
+
bundleId: iosBundleId,
|
|
3009
|
+
envVars,
|
|
3010
|
+
projectId,
|
|
3011
|
+
rawOutput: options.rawOutput
|
|
3012
|
+
}),
|
|
3013
|
+
target: {
|
|
3014
|
+
platform: "ios",
|
|
3015
|
+
distribution: iosProfile.distribution,
|
|
3016
|
+
artifactFormat: "ipa"
|
|
3017
|
+
},
|
|
3073
3018
|
bundleId: iosBundleId
|
|
3074
3019
|
};
|
|
3075
3020
|
});
|
|
3076
3021
|
const runAndroidPlatformBuild = (input) => Effect.gen(function* () {
|
|
3077
|
-
const { api, appMeta, envVars, profile
|
|
3078
|
-
if (!profile
|
|
3079
|
-
const androidProfile = profile
|
|
3022
|
+
const { api, appMeta, envVars, profile, projectId, projectRoot, tempDir } = input;
|
|
3023
|
+
if (!profile.android) return yield* new BuildProfileError({ message: `Profile "${profile.name}" has no android section.` });
|
|
3024
|
+
const androidProfile = profile.android;
|
|
3080
3025
|
const androidBundleId = appMeta.androidPackage;
|
|
3081
3026
|
if (!androidBundleId) return yield* new BuildProfileError({ message: "Missing expo.android.package in app.json." });
|
|
3082
|
-
const
|
|
3083
|
-
const gradleConfig = yield* readGradleConfig(androidDir);
|
|
3027
|
+
const gradleConfig = yield* readGradleConfig(`${projectRoot}/android`);
|
|
3084
3028
|
yield* warnOnGradleMismatch(gradleConfig, androidBundleId);
|
|
3085
3029
|
const applicationIdentifier = gradleConfig?.applicationId ?? androidBundleId;
|
|
3086
|
-
const build = yield* runAndroidBuild({
|
|
3087
|
-
api,
|
|
3088
|
-
tempDir,
|
|
3089
|
-
projectRoot,
|
|
3090
|
-
androidProfile,
|
|
3091
|
-
applicationIdentifier,
|
|
3092
|
-
envVars,
|
|
3093
|
-
projectId
|
|
3094
|
-
});
|
|
3095
|
-
const target = androidProfile.format === "aab" ? {
|
|
3096
|
-
platform: "android",
|
|
3097
|
-
distribution: "play-store",
|
|
3098
|
-
artifactFormat: "aab"
|
|
3099
|
-
} : {
|
|
3100
|
-
platform: "android",
|
|
3101
|
-
distribution: "direct",
|
|
3102
|
-
artifactFormat: "apk"
|
|
3103
|
-
};
|
|
3104
3030
|
return {
|
|
3105
|
-
build
|
|
3106
|
-
|
|
3031
|
+
build: yield* runAndroidBuild({
|
|
3032
|
+
api,
|
|
3033
|
+
tempDir,
|
|
3034
|
+
projectRoot,
|
|
3035
|
+
androidProfile,
|
|
3036
|
+
applicationIdentifier,
|
|
3037
|
+
envVars,
|
|
3038
|
+
projectId
|
|
3039
|
+
}),
|
|
3040
|
+
target: androidProfile.format === "aab" ? {
|
|
3041
|
+
platform: "android",
|
|
3042
|
+
distribution: "play-store",
|
|
3043
|
+
artifactFormat: "aab"
|
|
3044
|
+
} : {
|
|
3045
|
+
platform: "android",
|
|
3046
|
+
distribution: "direct",
|
|
3047
|
+
artifactFormat: "apk"
|
|
3048
|
+
},
|
|
3107
3049
|
bundleId: applicationIdentifier
|
|
3108
3050
|
};
|
|
3109
3051
|
});
|
|
3110
3052
|
const runPlatformBuild = (input) => input.options.platform === "ios" ? runIosPlatformBuild(input) : runAndroidPlatformBuild(input);
|
|
3111
3053
|
const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
|
|
3112
3054
|
const api = yield* apiClient;
|
|
3113
|
-
const
|
|
3114
|
-
const projectRoot = yield* runtime.cwd;
|
|
3055
|
+
const projectRoot = yield* (yield* CliRuntime).cwd;
|
|
3115
3056
|
const appJson = yield* readAppJson;
|
|
3116
3057
|
const projectId = yield* readProjectId;
|
|
3117
|
-
const profile
|
|
3058
|
+
const profile = yield* readBuildProfile(appJson, options.profileName);
|
|
3118
3059
|
const envVars = yield* pullEnvVars(api, {
|
|
3119
3060
|
projectId,
|
|
3120
|
-
environment: profile
|
|
3061
|
+
environment: profile.environment
|
|
3121
3062
|
});
|
|
3122
3063
|
const expoConfig = yield* readExpoConfig(projectRoot, envVars);
|
|
3123
3064
|
const appMeta = expoConfig ? yield* readAppMetaFromConfig(expoConfig, options.platform).pipe(Effect.tap(() => Console.log("Resolved app config via @expo/config")), Effect.catchAll(() => readAppMeta(appJson, options.platform))) : yield* readAppMeta(appJson, options.platform);
|
|
@@ -3127,18 +3068,17 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
|
|
|
3127
3068
|
projectRoot
|
|
3128
3069
|
});
|
|
3129
3070
|
const tempDir = yield* acquireBuildTempDir;
|
|
3130
|
-
yield* Console.log(`Building ${options.platform} artifact for profile "${profile
|
|
3131
|
-
const
|
|
3071
|
+
yield* Console.log(`Building ${options.platform} artifact for profile "${profile.name}" (runtimeVersion=${runtimeVersion})`);
|
|
3072
|
+
const { build, target, bundleId } = yield* runPlatformBuild({
|
|
3132
3073
|
api,
|
|
3133
3074
|
options,
|
|
3134
|
-
profile
|
|
3075
|
+
profile,
|
|
3135
3076
|
appMeta,
|
|
3136
3077
|
envVars,
|
|
3137
3078
|
projectId,
|
|
3138
3079
|
projectRoot,
|
|
3139
3080
|
tempDir
|
|
3140
3081
|
});
|
|
3141
|
-
const { build, target, bundleId } = outcome;
|
|
3142
3082
|
yield* Console.log(`Artifact produced: ${build.artifactPath}`);
|
|
3143
3083
|
if (options.noUpload) {
|
|
3144
3084
|
yield* printKeyValue([
|
|
@@ -3158,7 +3098,7 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
|
|
|
3158
3098
|
const result = yield* reserveAndUpload(api, {
|
|
3159
3099
|
target,
|
|
3160
3100
|
projectId,
|
|
3161
|
-
profileName: profile
|
|
3101
|
+
profileName: profile.name,
|
|
3162
3102
|
runtimeVersion,
|
|
3163
3103
|
...appMeta.appVersion === void 0 ? {} : { appVersion: appMeta.appVersion },
|
|
3164
3104
|
...appMeta.buildNumber === void 0 ? {} : { buildNumber: appMeta.buildNumber },
|
|
@@ -3174,7 +3114,7 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
|
|
|
3174
3114
|
["Build ID", result.id],
|
|
3175
3115
|
["Status", result.status],
|
|
3176
3116
|
["Platform", options.platform],
|
|
3177
|
-
["Profile", profile
|
|
3117
|
+
["Profile", profile.name],
|
|
3178
3118
|
["Runtime version", runtimeVersion],
|
|
3179
3119
|
["Artifact", build.artifactPath],
|
|
3180
3120
|
["SHA-256", build.sha256],
|
|
@@ -3228,8 +3168,7 @@ const handleBuildsCommandErrors = makeCommandErrorHandler();
|
|
|
3228
3168
|
//#region src/commands/builds/compatibility-matrix.ts
|
|
3229
3169
|
const compatibilityMatrixCommand = Command.make("compatibility-matrix", {}, () => Effect.gen(function* () {
|
|
3230
3170
|
const projectId = yield* readProjectId;
|
|
3231
|
-
const
|
|
3232
|
-
const result = yield* api.builds.compatibilityMatrix({ urlParams: { projectId } });
|
|
3171
|
+
const result = yield* (yield* apiClient).builds.compatibilityMatrix({ urlParams: { projectId } });
|
|
3233
3172
|
if (result.rows.length === 0 && result.missingRuntimeVersions.length === 0) {
|
|
3234
3173
|
yield* Console.log("No compatibility data found.");
|
|
3235
3174
|
return;
|
|
@@ -3245,7 +3184,7 @@ const compatibilityMatrixCommand = Command.make("compatibility-matrix", {}, () =
|
|
|
3245
3184
|
row.id,
|
|
3246
3185
|
row.platform,
|
|
3247
3186
|
row.runtimeVersion ?? "-",
|
|
3248
|
-
row.channels.map((channel
|
|
3187
|
+
row.channels.map((channel) => channel.channelName).join(", ") || "-"
|
|
3249
3188
|
]));
|
|
3250
3189
|
}
|
|
3251
3190
|
if (result.missingRuntimeVersions.length > 0) {
|
|
@@ -3255,11 +3194,11 @@ const compatibilityMatrixCommand = Command.make("compatibility-matrix", {}, () =
|
|
|
3255
3194
|
"Platform",
|
|
3256
3195
|
"Runtime Version",
|
|
3257
3196
|
"Updates"
|
|
3258
|
-
], result.missingRuntimeVersions.map((missing
|
|
3259
|
-
missing
|
|
3260
|
-
missing
|
|
3261
|
-
missing
|
|
3262
|
-
String(missing
|
|
3197
|
+
], result.missingRuntimeVersions.map((missing) => [
|
|
3198
|
+
missing.channelName,
|
|
3199
|
+
missing.platform,
|
|
3200
|
+
missing.runtimeVersion,
|
|
3201
|
+
String(missing.updateCount)
|
|
3263
3202
|
]));
|
|
3264
3203
|
}
|
|
3265
3204
|
}).pipe(handleBuildsCommandErrors));
|
|
@@ -3268,8 +3207,7 @@ const compatibilityMatrixCommand = Command.make("compatibility-matrix", {}, () =
|
|
|
3268
3207
|
//#region src/commands/builds/delete.ts
|
|
3269
3208
|
const id$8 = Args.text({ name: "id" });
|
|
3270
3209
|
const deleteCommand$5 = Command.make("delete", { id: id$8 }, (opts) => Effect.gen(function* () {
|
|
3271
|
-
|
|
3272
|
-
yield* api.builds.delete({ path: { id: opts.id } });
|
|
3210
|
+
yield* (yield* apiClient).builds.delete({ path: { id: opts.id } });
|
|
3273
3211
|
yield* Console.log(`Build ${opts.id} deleted.`);
|
|
3274
3212
|
}).pipe(handleBuildsCommandErrors));
|
|
3275
3213
|
|
|
@@ -3277,8 +3215,7 @@ const deleteCommand$5 = Command.make("delete", { id: id$8 }, (opts) => Effect.ge
|
|
|
3277
3215
|
//#region src/commands/builds/get.ts
|
|
3278
3216
|
const id$7 = Args.text({ name: "id" });
|
|
3279
3217
|
const getCommand$2 = Command.make("get", { id: id$7 }, (opts) => Effect.gen(function* () {
|
|
3280
|
-
const
|
|
3281
|
-
const build = yield* api.builds.get({ path: { id: opts.id } });
|
|
3218
|
+
const build = yield* (yield* apiClient).builds.get({ path: { id: opts.id } });
|
|
3282
3219
|
yield* printKeyValue([
|
|
3283
3220
|
["ID", build.id],
|
|
3284
3221
|
["Platform", build.platform],
|
|
@@ -3299,8 +3236,7 @@ const getCommand$2 = Command.make("get", { id: id$7 }, (opts) => Effect.gen(func
|
|
|
3299
3236
|
//#region src/commands/builds/install-link.ts
|
|
3300
3237
|
const id$6 = Args.text({ name: "id" });
|
|
3301
3238
|
const installLinkCommand = Command.make("install-link", { id: id$6 }, (opts) => Effect.gen(function* () {
|
|
3302
|
-
const
|
|
3303
|
-
const result = yield* api.builds.getInstallLink({ path: { id: opts.id } });
|
|
3239
|
+
const result = yield* (yield* apiClient).builds.getInstallLink({ path: { id: opts.id } });
|
|
3304
3240
|
yield* printKeyValue([
|
|
3305
3241
|
["Artifact URL", result.artifactUrl],
|
|
3306
3242
|
["Install URL", result.installUrl ?? "-"],
|
|
@@ -3367,7 +3303,7 @@ const resolveNamedResourceId$2 = (params, makeError) => Effect.gen(function* ()
|
|
|
3367
3303
|
//#region src/commands/channels/helpers.ts
|
|
3368
3304
|
var ChannelCommandError = class extends Data.TaggedError("ChannelCommandError") {};
|
|
3369
3305
|
const handleChannelCommandErrors = makeCommandErrorHandler({ ChannelCommandError: 2 });
|
|
3370
|
-
const resolveNamedResourceId$1 = (params) => resolveNamedResourceId$2(params, (message
|
|
3306
|
+
const resolveNamedResourceId$1 = (params) => resolveNamedResourceId$2(params, (message) => new ChannelCommandError({ message }));
|
|
3371
3307
|
|
|
3372
3308
|
//#endregion
|
|
3373
3309
|
//#region src/commands/channels/create.ts
|
|
@@ -3389,16 +3325,16 @@ const createCommand$2 = Command.make("create", {
|
|
|
3389
3325
|
kind: "Branch",
|
|
3390
3326
|
name: opts.branch
|
|
3391
3327
|
});
|
|
3392
|
-
const channel
|
|
3328
|
+
const channel = yield* api.channels.create({ payload: {
|
|
3393
3329
|
projectId,
|
|
3394
3330
|
name: opts.name,
|
|
3395
3331
|
branchId
|
|
3396
3332
|
} });
|
|
3397
3333
|
yield* printKeyValue([
|
|
3398
|
-
["ID", channel
|
|
3399
|
-
["Name", channel
|
|
3334
|
+
["ID", channel.id],
|
|
3335
|
+
["Name", channel.name],
|
|
3400
3336
|
["Branch", opts.branch],
|
|
3401
|
-
["Created", channel
|
|
3337
|
+
["Created", channel.createdAt]
|
|
3402
3338
|
]);
|
|
3403
3339
|
}).pipe(handleChannelCommandErrors));
|
|
3404
3340
|
|
|
@@ -3406,8 +3342,7 @@ const createCommand$2 = Command.make("create", {
|
|
|
3406
3342
|
//#region src/commands/channels/delete.ts
|
|
3407
3343
|
const id$5 = Args.text({ name: "id" });
|
|
3408
3344
|
const deleteCommand$4 = Command.make("delete", { id: id$5 }, (opts) => Effect.gen(function* () {
|
|
3409
|
-
|
|
3410
|
-
yield* api.channels.delete({ path: { id: opts.id } });
|
|
3345
|
+
yield* (yield* apiClient).channels.delete({ path: { id: opts.id } });
|
|
3411
3346
|
yield* Console.log(`Channel ${opts.id} deleted.`);
|
|
3412
3347
|
}).pipe(handleChannelCommandErrors));
|
|
3413
3348
|
|
|
@@ -3429,7 +3364,7 @@ const listCommand$4 = Command.make("list", {}, () => Effect.gen(function* () {
|
|
|
3429
3364
|
yield* Console.log("No channels found.");
|
|
3430
3365
|
return;
|
|
3431
3366
|
}
|
|
3432
|
-
const branchNames = new Map(branches.map((branch
|
|
3367
|
+
const branchNames = new Map(branches.map((branch) => [branch.id, branch.name]));
|
|
3433
3368
|
yield* printTable([
|
|
3434
3369
|
"ID",
|
|
3435
3370
|
"Name",
|
|
@@ -3437,13 +3372,13 @@ const listCommand$4 = Command.make("list", {}, () => Effect.gen(function* () {
|
|
|
3437
3372
|
"Paused",
|
|
3438
3373
|
"Rollout",
|
|
3439
3374
|
"Created"
|
|
3440
|
-
], items.map((channel
|
|
3441
|
-
channel
|
|
3442
|
-
channel
|
|
3443
|
-
branchNames.get(channel
|
|
3444
|
-
channel
|
|
3445
|
-
channel
|
|
3446
|
-
channel
|
|
3375
|
+
], items.map((channel) => [
|
|
3376
|
+
channel.id,
|
|
3377
|
+
channel.name,
|
|
3378
|
+
branchNames.get(channel.branchId) ?? channel.branchId,
|
|
3379
|
+
channel.isPaused ? "yes" : "no",
|
|
3380
|
+
channel.branchMappingJson === null ? "-" : "active",
|
|
3381
|
+
channel.createdAt
|
|
3447
3382
|
]));
|
|
3448
3383
|
}).pipe(handleChannelCommandErrors));
|
|
3449
3384
|
|
|
@@ -3451,27 +3386,24 @@ const listCommand$4 = Command.make("list", {}, () => Effect.gen(function* () {
|
|
|
3451
3386
|
//#region src/commands/channels/pause.ts
|
|
3452
3387
|
const id$4 = Args.text({ name: "id" });
|
|
3453
3388
|
const pauseCommand = Command.make("pause", { id: id$4 }, (opts) => Effect.gen(function* () {
|
|
3454
|
-
const
|
|
3455
|
-
|
|
3456
|
-
yield* Console.log(`Channel "${channel$2.name}" paused.`);
|
|
3389
|
+
const channel = yield* (yield* apiClient).channels.pause({ path: { id: opts.id } });
|
|
3390
|
+
yield* Console.log(`Channel "${channel.name}" paused.`);
|
|
3457
3391
|
}).pipe(handleChannelCommandErrors));
|
|
3458
3392
|
|
|
3459
3393
|
//#endregion
|
|
3460
3394
|
//#region src/commands/channels/resume.ts
|
|
3461
3395
|
const id$3 = Args.text({ name: "id" });
|
|
3462
3396
|
const resumeCommand = Command.make("resume", { id: id$3 }, (opts) => Effect.gen(function* () {
|
|
3463
|
-
const
|
|
3464
|
-
|
|
3465
|
-
yield* Console.log(`Channel "${channel$2.name}" resumed.`);
|
|
3397
|
+
const channel = yield* (yield* apiClient).channels.resume({ path: { id: opts.id } });
|
|
3398
|
+
yield* Console.log(`Channel "${channel.name}" resumed.`);
|
|
3466
3399
|
}).pipe(handleChannelCommandErrors));
|
|
3467
3400
|
|
|
3468
3401
|
//#endregion
|
|
3469
3402
|
//#region src/commands/channels/rollout/complete.ts
|
|
3470
3403
|
const channelId$3 = Args.text({ name: "channelId" });
|
|
3471
3404
|
const completeCommand$1 = Command.make("complete", { channelId: channelId$3 }, (opts) => Effect.gen(function* () {
|
|
3472
|
-
const
|
|
3473
|
-
|
|
3474
|
-
yield* Console.log(`Completed rollout on channel "${channel$2.name}".`);
|
|
3405
|
+
const channel = yield* (yield* apiClient).channels.completeBranchRollout({ path: { id: opts.channelId } });
|
|
3406
|
+
yield* Console.log(`Completed rollout on channel "${channel.name}".`);
|
|
3475
3407
|
}).pipe(handleChannelCommandErrors));
|
|
3476
3408
|
|
|
3477
3409
|
//#endregion
|
|
@@ -3480,7 +3412,7 @@ const RolloutPercentage = Schema.Number.pipe(Schema.int(), Schema.between(1, 100
|
|
|
3480
3412
|
message: () => "Rollout percentage must be between 1 and 100.",
|
|
3481
3413
|
identifier: "RolloutPercentage"
|
|
3482
3414
|
});
|
|
3483
|
-
const rolloutPercentageOption = (name
|
|
3415
|
+
const rolloutPercentageOption = (name) => Options.integer(name).pipe(Options.withSchema(RolloutPercentage));
|
|
3484
3416
|
const KeyValuePair = Schema.Struct({
|
|
3485
3417
|
key: Schema.String,
|
|
3486
3418
|
value: Schema.String
|
|
@@ -3497,7 +3429,7 @@ const KeyValueFromString = Schema.transformOrFail(Schema.String, KeyValuePair, {
|
|
|
3497
3429
|
},
|
|
3498
3430
|
encode: ({ key, value }) => ParseResult.succeed(`${key}=${value}`)
|
|
3499
3431
|
});
|
|
3500
|
-
const keyValueArg = (name
|
|
3432
|
+
const keyValueArg = (name) => Args.text({ name }).pipe(Args.withSchema(KeyValueFromString));
|
|
3501
3433
|
|
|
3502
3434
|
//#endregion
|
|
3503
3435
|
//#region src/commands/channels/rollout/create.ts
|
|
@@ -3521,23 +3453,22 @@ const createCommand$1 = Command.make("create", {
|
|
|
3521
3453
|
kind: "Branch",
|
|
3522
3454
|
name: opts.branch
|
|
3523
3455
|
});
|
|
3524
|
-
const channel
|
|
3456
|
+
const channel = yield* api.channels.createBranchRollout({
|
|
3525
3457
|
path: { id: opts.channelId },
|
|
3526
3458
|
payload: {
|
|
3527
3459
|
newBranchId,
|
|
3528
3460
|
percentage: opts.percentage
|
|
3529
3461
|
}
|
|
3530
3462
|
});
|
|
3531
|
-
yield* Console.log(`Started rollout on channel "${channel
|
|
3463
|
+
yield* Console.log(`Started rollout on channel "${channel.name}" to branch "${opts.branch}" at ${String(opts.percentage)}%.`);
|
|
3532
3464
|
}).pipe(handleChannelCommandErrors));
|
|
3533
3465
|
|
|
3534
3466
|
//#endregion
|
|
3535
3467
|
//#region src/commands/channels/rollout/revert.ts
|
|
3536
3468
|
const channelId$1 = Args.text({ name: "channelId" });
|
|
3537
3469
|
const revertCommand$1 = Command.make("revert", { channelId: channelId$1 }, (opts) => Effect.gen(function* () {
|
|
3538
|
-
const
|
|
3539
|
-
|
|
3540
|
-
yield* Console.log(`Reverted rollout on channel "${channel$2.name}".`);
|
|
3470
|
+
const channel = yield* (yield* apiClient).channels.revertBranchRollout({ path: { id: opts.channelId } });
|
|
3471
|
+
yield* Console.log(`Reverted rollout on channel "${channel.name}".`);
|
|
3541
3472
|
}).pipe(handleChannelCommandErrors));
|
|
3542
3473
|
|
|
3543
3474
|
//#endregion
|
|
@@ -3548,12 +3479,11 @@ const updateCommand$2 = Command.make("update", {
|
|
|
3548
3479
|
channelId,
|
|
3549
3480
|
percentage: percentage$1
|
|
3550
3481
|
}, (opts) => Effect.gen(function* () {
|
|
3551
|
-
const
|
|
3552
|
-
const channel$2 = yield* api.channels.updateBranchRollout({
|
|
3482
|
+
const channel = yield* (yield* apiClient).channels.updateBranchRollout({
|
|
3553
3483
|
path: { id: opts.channelId },
|
|
3554
3484
|
payload: { percentage: opts.percentage }
|
|
3555
3485
|
});
|
|
3556
|
-
yield* Console.log(`Updated rollout on channel "${channel
|
|
3486
|
+
yield* Console.log(`Updated rollout on channel "${channel.name}" to ${String(opts.percentage)}%.`);
|
|
3557
3487
|
}).pipe(handleChannelCommandErrors));
|
|
3558
3488
|
|
|
3559
3489
|
//#endregion
|
|
@@ -3585,11 +3515,11 @@ const updateCommand$1 = Command.make("update", {
|
|
|
3585
3515
|
kind: "Branch",
|
|
3586
3516
|
name: opts.branch
|
|
3587
3517
|
});
|
|
3588
|
-
const channel
|
|
3518
|
+
const channel = yield* api.channels.update({
|
|
3589
3519
|
path: { id: opts.id },
|
|
3590
3520
|
payload: { branchId }
|
|
3591
3521
|
});
|
|
3592
|
-
yield* Console.log(`Channel "${channel
|
|
3522
|
+
yield* Console.log(`Channel "${channel.name}" relinked to branch "${opts.branch}".`);
|
|
3593
3523
|
}).pipe(handleChannelCommandErrors));
|
|
3594
3524
|
|
|
3595
3525
|
//#endregion
|
|
@@ -3609,32 +3539,23 @@ const channelsCommand = Command.make("channels", {}, () => Console.log("Manage c
|
|
|
3609
3539
|
const APPLE_TEAM_ID_RE = /^[A-Z0-9]{10}$/u;
|
|
3610
3540
|
const extractTeamId = (params) => {
|
|
3611
3541
|
if (params.orgUnit && APPLE_TEAM_ID_RE.test(params.orgUnit)) return params.orgUnit;
|
|
3612
|
-
|
|
3613
|
-
return parenMatch?.[1];
|
|
3542
|
+
return /\(([A-Z0-9]{10})\)\s*$/u.exec(params.signingIdentity)?.[1];
|
|
3614
3543
|
};
|
|
3615
3544
|
/**
|
|
3616
3545
|
* Parse a PKCS#12 (.p12) buffer and extract certificate metadata.
|
|
3617
3546
|
*/
|
|
3618
3547
|
const inspectP12 = (params) => Effect.try({
|
|
3619
3548
|
try: () => {
|
|
3620
|
-
const
|
|
3621
|
-
const cert = getX509Certificate(p12);
|
|
3549
|
+
const cert = getX509Certificate(parsePKCS12(params.data, params.password));
|
|
3622
3550
|
const serialNumber = getFormattedSerialNumber(cert) ?? "unknown";
|
|
3623
3551
|
const validFrom = cert.validity.notBefore instanceof Date ? cert.validity.notBefore : void 0;
|
|
3624
3552
|
const expiresAt = cert.validity.notAfter instanceof Date ? cert.validity.notAfter : void 0;
|
|
3625
|
-
const
|
|
3626
|
-
const subject = subjectParts.join(", ");
|
|
3553
|
+
const subject = cert.subject.attributes.map((attr) => `${attr.shortName ?? attr.name}=${String(attr.value)}`).join(", ");
|
|
3627
3554
|
const issuerCNValue = cert.issuer.getField("CN")?.value;
|
|
3628
3555
|
const issuerCN = typeof issuerCNValue === "string" ? issuerCNValue : void 0;
|
|
3629
3556
|
const cnValue = cert.subject.getField("CN")?.value;
|
|
3630
|
-
const
|
|
3631
|
-
const signingIdentity = cn ?? subject;
|
|
3557
|
+
const signingIdentity = (typeof cnValue === "string" ? cnValue : void 0) ?? subject;
|
|
3632
3558
|
const orgUnitValue = cert.subject.getField("OU")?.value;
|
|
3633
|
-
const orgUnit = typeof orgUnitValue === "string" ? orgUnitValue : void 0;
|
|
3634
|
-
const teamId = extractTeamId({
|
|
3635
|
-
signingIdentity,
|
|
3636
|
-
orgUnit
|
|
3637
|
-
});
|
|
3638
3559
|
return {
|
|
3639
3560
|
serialNumber,
|
|
3640
3561
|
validFrom,
|
|
@@ -3642,7 +3563,10 @@ const inspectP12 = (params) => Effect.try({
|
|
|
3642
3563
|
subject,
|
|
3643
3564
|
issuerCN,
|
|
3644
3565
|
signingIdentity,
|
|
3645
|
-
teamId
|
|
3566
|
+
teamId: extractTeamId({
|
|
3567
|
+
signingIdentity,
|
|
3568
|
+
orgUnit: typeof orgUnitValue === "string" ? orgUnitValue : void 0
|
|
3569
|
+
})
|
|
3646
3570
|
};
|
|
3647
3571
|
},
|
|
3648
3572
|
catch: (error) => new CredentialValidationError({ message: `Failed to parse P12 certificate: ${error instanceof Error ? error.message : String(error)}` })
|
|
@@ -3660,7 +3584,7 @@ const listAllCredentials = (api) => Effect.gen(function* () {
|
|
|
3660
3584
|
api.androidUploadKeystores.list(),
|
|
3661
3585
|
api.googleServiceAccountKeys.list()
|
|
3662
3586
|
], { concurrency: "unbounded" });
|
|
3663
|
-
|
|
3587
|
+
return [
|
|
3664
3588
|
...certs.items.map((cert) => ({
|
|
3665
3589
|
id: cert.id,
|
|
3666
3590
|
name: cert.serialNumber,
|
|
@@ -3682,12 +3606,12 @@ const listAllCredentials = (api) => Effect.gen(function* () {
|
|
|
3682
3606
|
type: "asc-api-key",
|
|
3683
3607
|
distribution: null
|
|
3684
3608
|
})),
|
|
3685
|
-
...profiles.items.map((profile
|
|
3686
|
-
id: profile
|
|
3687
|
-
name: profile
|
|
3609
|
+
...profiles.items.map((profile) => ({
|
|
3610
|
+
id: profile.id,
|
|
3611
|
+
name: profile.profileName ?? profile.bundleIdentifier,
|
|
3688
3612
|
platform: "ios",
|
|
3689
3613
|
type: "provisioning-profile",
|
|
3690
|
-
distribution: formatDistribution(profile
|
|
3614
|
+
distribution: formatDistribution(profile.distributionType)
|
|
3691
3615
|
})),
|
|
3692
3616
|
...keystores.items.map((ks) => ({
|
|
3693
3617
|
id: ks.id,
|
|
@@ -3704,7 +3628,6 @@ const listAllCredentials = (api) => Effect.gen(function* () {
|
|
|
3704
3628
|
distribution: null
|
|
3705
3629
|
}))
|
|
3706
3630
|
];
|
|
3707
|
-
return rows;
|
|
3708
3631
|
});
|
|
3709
3632
|
const filterCredentials = (rows, filter) => rows.filter((row) => {
|
|
3710
3633
|
if (filter.platform && row.platform !== filter.platform) return false;
|
|
@@ -3722,16 +3645,15 @@ const uploadIosDistributionCertificate = (api, input, bytes) => Effect.gen(funct
|
|
|
3722
3645
|
});
|
|
3723
3646
|
if (!info.teamId) return yield* new CredentialValidationError({ message: "Could not derive Apple Team ID from certificate subject (expected OU=TEAMID or CN with (TEAMID))." });
|
|
3724
3647
|
if (!info.validFrom || !info.expiresAt) return yield* new CredentialValidationError({ message: "Certificate is missing notBefore/notAfter dates." });
|
|
3725
|
-
const created = yield* api.appleDistributionCertificates.upload({ payload: {
|
|
3726
|
-
p12Base64: toBase64(bytes),
|
|
3727
|
-
p12Password: input.password,
|
|
3728
|
-
serialNumber: info.serialNumber,
|
|
3729
|
-
appleTeamIdentifier: info.teamId,
|
|
3730
|
-
validFrom: info.validFrom.toISOString(),
|
|
3731
|
-
validUntil: info.expiresAt.toISOString()
|
|
3732
|
-
} });
|
|
3733
3648
|
return {
|
|
3734
|
-
id:
|
|
3649
|
+
id: (yield* api.appleDistributionCertificates.upload({ payload: {
|
|
3650
|
+
p12Base64: toBase64(bytes),
|
|
3651
|
+
p12Password: input.password,
|
|
3652
|
+
serialNumber: info.serialNumber,
|
|
3653
|
+
appleTeamIdentifier: info.teamId,
|
|
3654
|
+
validFrom: info.validFrom.toISOString(),
|
|
3655
|
+
validUntil: info.expiresAt.toISOString()
|
|
3656
|
+
} })).id,
|
|
3735
3657
|
name: input.name,
|
|
3736
3658
|
platform: "ios",
|
|
3737
3659
|
type: "distribution-certificate"
|
|
@@ -3740,13 +3662,12 @@ const uploadIosDistributionCertificate = (api, input, bytes) => Effect.gen(funct
|
|
|
3740
3662
|
const uploadIosPushKey = (api, input, bytes) => Effect.gen(function* () {
|
|
3741
3663
|
if (!input.keyId) return yield* missing("key-id");
|
|
3742
3664
|
if (!input.appleTeamIdentifier) return yield* missing("apple-team-identifier");
|
|
3743
|
-
const created = yield* api.applePushKeys.upload({ payload: {
|
|
3744
|
-
keyId: input.keyId,
|
|
3745
|
-
p8Pem: toUtf8(bytes),
|
|
3746
|
-
appleTeamIdentifier: input.appleTeamIdentifier
|
|
3747
|
-
} });
|
|
3748
3665
|
return {
|
|
3749
|
-
id:
|
|
3666
|
+
id: (yield* api.applePushKeys.upload({ payload: {
|
|
3667
|
+
keyId: input.keyId,
|
|
3668
|
+
p8Pem: toUtf8(bytes),
|
|
3669
|
+
appleTeamIdentifier: input.appleTeamIdentifier
|
|
3670
|
+
} })).id,
|
|
3750
3671
|
name: input.name,
|
|
3751
3672
|
platform: "ios",
|
|
3752
3673
|
type: "push-key"
|
|
@@ -3755,24 +3676,22 @@ const uploadIosPushKey = (api, input, bytes) => Effect.gen(function* () {
|
|
|
3755
3676
|
const uploadIosAscApiKey = (api, input, bytes) => Effect.gen(function* () {
|
|
3756
3677
|
if (!input.keyId) return yield* missing("key-id");
|
|
3757
3678
|
if (!input.issuerId) return yield* missing("issuer-id");
|
|
3758
|
-
const created = yield* api.ascApiKeys.upload({ payload: {
|
|
3759
|
-
name: input.name,
|
|
3760
|
-
keyId: input.keyId,
|
|
3761
|
-
issuerId: input.issuerId,
|
|
3762
|
-
p8Pem: toUtf8(bytes),
|
|
3763
|
-
...input.appleTeamIdentifier === void 0 ? {} : { appleTeamIdentifier: input.appleTeamIdentifier }
|
|
3764
|
-
} });
|
|
3765
3679
|
return {
|
|
3766
|
-
id:
|
|
3680
|
+
id: (yield* api.ascApiKeys.upload({ payload: {
|
|
3681
|
+
name: input.name,
|
|
3682
|
+
keyId: input.keyId,
|
|
3683
|
+
issuerId: input.issuerId,
|
|
3684
|
+
p8Pem: toUtf8(bytes),
|
|
3685
|
+
...input.appleTeamIdentifier === void 0 ? {} : { appleTeamIdentifier: input.appleTeamIdentifier }
|
|
3686
|
+
} })).id,
|
|
3767
3687
|
name: input.name,
|
|
3768
3688
|
platform: "ios",
|
|
3769
3689
|
type: "asc-api-key"
|
|
3770
3690
|
};
|
|
3771
3691
|
});
|
|
3772
3692
|
const uploadIosProvisioningProfile = (api, input, bytes) => Effect.gen(function* () {
|
|
3773
|
-
const created = yield* api.appleProvisioningProfiles.upload({ payload: { profileBase64: toBase64(bytes) } });
|
|
3774
3693
|
return {
|
|
3775
|
-
id:
|
|
3694
|
+
id: (yield* api.appleProvisioningProfiles.upload({ payload: { profileBase64: toBase64(bytes) } })).id,
|
|
3776
3695
|
name: input.name,
|
|
3777
3696
|
platform: "ios",
|
|
3778
3697
|
type: "provisioning-profile"
|
|
@@ -3782,23 +3701,21 @@ const uploadAndroidKeystore = (api, input, bytes) => Effect.gen(function* () {
|
|
|
3782
3701
|
if (input.password === void 0) return yield* missing("password");
|
|
3783
3702
|
if (!input.keyAlias) return yield* missing("key-alias");
|
|
3784
3703
|
if (!input.keyPassword) return yield* missing("key-password");
|
|
3785
|
-
const created = yield* api.androidUploadKeystores.upload({ payload: {
|
|
3786
|
-
keystoreBase64: toBase64(bytes),
|
|
3787
|
-
keyAlias: input.keyAlias,
|
|
3788
|
-
keystorePassword: input.password,
|
|
3789
|
-
keyPassword: input.keyPassword
|
|
3790
|
-
} });
|
|
3791
3704
|
return {
|
|
3792
|
-
id:
|
|
3705
|
+
id: (yield* api.androidUploadKeystores.upload({ payload: {
|
|
3706
|
+
keystoreBase64: toBase64(bytes),
|
|
3707
|
+
keyAlias: input.keyAlias,
|
|
3708
|
+
keystorePassword: input.password,
|
|
3709
|
+
keyPassword: input.keyPassword
|
|
3710
|
+
} })).id,
|
|
3793
3711
|
name: input.name,
|
|
3794
3712
|
platform: "android",
|
|
3795
3713
|
type: "keystore"
|
|
3796
3714
|
};
|
|
3797
3715
|
});
|
|
3798
3716
|
const uploadAndroidGoogleServiceAccountKey = (api, input, bytes) => Effect.gen(function* () {
|
|
3799
|
-
const created = yield* api.googleServiceAccountKeys.upload({ payload: { json: toUtf8(bytes) } });
|
|
3800
3717
|
return {
|
|
3801
|
-
id:
|
|
3718
|
+
id: (yield* api.googleServiceAccountKeys.upload({ payload: { json: toUtf8(bytes) } })).id,
|
|
3802
3719
|
name: input.name,
|
|
3803
3720
|
platform: "android",
|
|
3804
3721
|
type: "google-service-account-key"
|
|
@@ -3813,8 +3730,7 @@ const uploadHandlers = {
|
|
|
3813
3730
|
"android:google-service-account-key": uploadAndroidGoogleServiceAccountKey
|
|
3814
3731
|
};
|
|
3815
3732
|
const uploadCredential = (api, input) => Effect.gen(function* () {
|
|
3816
|
-
const
|
|
3817
|
-
const bytes = yield* fs.readFile(input.filePath);
|
|
3733
|
+
const bytes = yield* (yield* FileSystem.FileSystem).readFile(input.filePath);
|
|
3818
3734
|
const key = `${input.platform}:${input.type}`;
|
|
3819
3735
|
const hasKey = (candidate) => Object.hasOwn(uploadHandlers, candidate);
|
|
3820
3736
|
const handler = hasKey(key) ? uploadHandlers[key] : void 0;
|
|
@@ -3822,29 +3738,29 @@ const uploadCredential = (api, input) => Effect.gen(function* () {
|
|
|
3822
3738
|
return yield* handler(api, input, bytes);
|
|
3823
3739
|
});
|
|
3824
3740
|
const deleteCredential = (api, input) => {
|
|
3825
|
-
const path
|
|
3741
|
+
const path = { id: input.id };
|
|
3826
3742
|
return Match.value({
|
|
3827
3743
|
platform: input.platform,
|
|
3828
3744
|
type: input.type
|
|
3829
3745
|
}).pipe(Match.when({
|
|
3830
3746
|
platform: "ios",
|
|
3831
3747
|
type: "distribution-certificate"
|
|
3832
|
-
}, () => api.appleDistributionCertificates.delete({ path
|
|
3748
|
+
}, () => api.appleDistributionCertificates.delete({ path })), Match.when({
|
|
3833
3749
|
platform: "ios",
|
|
3834
3750
|
type: "push-key"
|
|
3835
|
-
}, () => api.applePushKeys.delete({ path
|
|
3751
|
+
}, () => api.applePushKeys.delete({ path })), Match.when({
|
|
3836
3752
|
platform: "ios",
|
|
3837
3753
|
type: "asc-api-key"
|
|
3838
|
-
}, () => api.ascApiKeys.delete({ path
|
|
3754
|
+
}, () => api.ascApiKeys.delete({ path })), Match.when({
|
|
3839
3755
|
platform: "ios",
|
|
3840
3756
|
type: "provisioning-profile"
|
|
3841
|
-
}, () => api.appleProvisioningProfiles.delete({ path
|
|
3757
|
+
}, () => api.appleProvisioningProfiles.delete({ path })), Match.when({
|
|
3842
3758
|
platform: "android",
|
|
3843
3759
|
type: "keystore"
|
|
3844
|
-
}, () => api.androidUploadKeystores.delete({ path
|
|
3760
|
+
}, () => api.androidUploadKeystores.delete({ path })), Match.when({
|
|
3845
3761
|
platform: "android",
|
|
3846
3762
|
type: "google-service-account-key"
|
|
3847
|
-
}, () => api.googleServiceAccountKeys.delete({ path
|
|
3763
|
+
}, () => api.googleServiceAccountKeys.delete({ path })), Match.orElse(() => Effect.fail(new CredentialValidationError({ message: `Unsupported credential combination: platform=${input.platform} type=${input.type}` }))));
|
|
3848
3764
|
};
|
|
3849
3765
|
|
|
3850
3766
|
//#endregion
|
|
@@ -3864,8 +3780,7 @@ const deleteCommand$3 = Command.make("delete", {
|
|
|
3864
3780
|
platform: platform$4,
|
|
3865
3781
|
type: type$1
|
|
3866
3782
|
}, (opts) => Effect.gen(function* () {
|
|
3867
|
-
|
|
3868
|
-
yield* deleteCredential(api, {
|
|
3783
|
+
yield* deleteCredential(yield* apiClient, {
|
|
3869
3784
|
id: opts.id,
|
|
3870
3785
|
platform: opts.platform,
|
|
3871
3786
|
type: opts.type
|
|
@@ -3877,9 +3792,7 @@ const deleteCommand$3 = Command.make("delete", {
|
|
|
3877
3792
|
//#region src/commands/credentials/list.ts
|
|
3878
3793
|
const platform$3 = Options.choice("platform", ["ios", "android"]).pipe(Options.optional);
|
|
3879
3794
|
const listCommand$3 = Command.make("list", { platform: platform$3 }, (opts) => Effect.gen(function* () {
|
|
3880
|
-
const
|
|
3881
|
-
const rows = yield* listAllCredentials(api);
|
|
3882
|
-
const filtered = filterCredentials(rows, Option.match(opts.platform, {
|
|
3795
|
+
const filtered = filterCredentials(yield* listAllCredentials(yield* apiClient), Option.match(opts.platform, {
|
|
3883
3796
|
onNone: () => ({}),
|
|
3884
3797
|
onSome: (platformValue) => ({ platform: platformValue })
|
|
3885
3798
|
}));
|
|
@@ -3940,7 +3853,7 @@ const uploadCommand = Command.make("upload", {
|
|
|
3940
3853
|
const keyIdOpt = Option.getOrUndefined(opts.keyId);
|
|
3941
3854
|
const issuerIdOpt = Option.getOrUndefined(opts.issuerId);
|
|
3942
3855
|
const appleTeamIdentifierOpt = Option.getOrUndefined(opts.appleTeamIdentifier);
|
|
3943
|
-
const
|
|
3856
|
+
const credential = yield* uploadCredential(api, {
|
|
3944
3857
|
platform: opts.platform,
|
|
3945
3858
|
type: opts.type,
|
|
3946
3859
|
name: opts.name,
|
|
@@ -3951,8 +3864,7 @@ const uploadCommand = Command.make("upload", {
|
|
|
3951
3864
|
...keyIdOpt === void 0 ? {} : { keyId: keyIdOpt },
|
|
3952
3865
|
...issuerIdOpt === void 0 ? {} : { issuerId: issuerIdOpt },
|
|
3953
3866
|
...appleTeamIdentifierOpt === void 0 ? {} : { appleTeamIdentifier: appleTeamIdentifierOpt }
|
|
3954
|
-
};
|
|
3955
|
-
const credential = yield* uploadCredential(api, input);
|
|
3867
|
+
});
|
|
3956
3868
|
yield* Console.log("Credential uploaded successfully.");
|
|
3957
3869
|
yield* Console.log("");
|
|
3958
3870
|
yield* printKeyValue([
|
|
@@ -3987,29 +3899,26 @@ const environmentOption$5 = Options.text("environment").pipe(Options.withDefault
|
|
|
3987
3899
|
const deleteCommand$2 = Command.make("delete", {
|
|
3988
3900
|
key: keyArg,
|
|
3989
3901
|
environment: environmentOption$5
|
|
3990
|
-
}, ({ key, environment
|
|
3902
|
+
}, ({ key, environment }) => Effect.gen(function* () {
|
|
3991
3903
|
const projectId = yield* readProjectId;
|
|
3992
3904
|
const api = yield* apiClient;
|
|
3993
|
-
const
|
|
3905
|
+
const match = (yield* api["env-vars"].list({ urlParams: {
|
|
3994
3906
|
projectId,
|
|
3995
|
-
environment
|
|
3996
|
-
} });
|
|
3997
|
-
|
|
3998
|
-
if (!match) return yield* new EnvResourceNotFoundError({ message: `Environment variable ${key} not found in ${environment$1}` });
|
|
3907
|
+
environment
|
|
3908
|
+
} })).items.find((item) => item.key === key);
|
|
3909
|
+
if (!match) return yield* new EnvResourceNotFoundError({ message: `Environment variable ${key} not found in ${environment}` });
|
|
3999
3910
|
yield* api["env-vars"].delete({ path: { id: match.id } });
|
|
4000
|
-
yield* Console.log(`Deleted ${key} from ${environment
|
|
4001
|
-
return void 0;
|
|
3911
|
+
yield* Console.log(`Deleted ${key} from ${environment}`);
|
|
4002
3912
|
}).pipe(handleEnvCommandErrors));
|
|
4003
3913
|
|
|
4004
3914
|
//#endregion
|
|
4005
3915
|
//#region src/commands/env/export.ts
|
|
4006
3916
|
const environmentOption$4 = Options.text("environment").pipe(Options.withDefault("production"));
|
|
4007
|
-
const exportCommand = Command.make("export", { environment: environmentOption$4 }, ({ environment
|
|
3917
|
+
const exportCommand = Command.make("export", { environment: environmentOption$4 }, ({ environment }) => Effect.gen(function* () {
|
|
4008
3918
|
const projectId = yield* readProjectId;
|
|
4009
|
-
const
|
|
4010
|
-
const result = yield* api["env-vars"].export({ urlParams: {
|
|
3919
|
+
const result = yield* (yield* apiClient)["env-vars"].export({ urlParams: {
|
|
4011
3920
|
projectId,
|
|
4012
|
-
environment
|
|
3921
|
+
environment
|
|
4013
3922
|
} });
|
|
4014
3923
|
for (const item of result.items) {
|
|
4015
3924
|
const escaped = item.value.replaceAll("'", String.raw`'\''`);
|
|
@@ -4021,8 +3930,7 @@ const exportCommand = Command.make("export", { environment: environmentOption$4
|
|
|
4021
3930
|
//#region src/commands/env/get.ts
|
|
4022
3931
|
const id = Args.text({ name: "id" });
|
|
4023
3932
|
const getCommand$1 = Command.make("get", { id }, (opts) => Effect.gen(function* () {
|
|
4024
|
-
const
|
|
4025
|
-
const envVar = yield* api["env-vars"].get({ path: { id: opts.id } });
|
|
3933
|
+
const envVar = yield* (yield* apiClient)["env-vars"].get({ path: { id: opts.id } });
|
|
4026
3934
|
yield* printKeyValue([
|
|
4027
3935
|
["ID", envVar.id],
|
|
4028
3936
|
["Key", envVar.key],
|
|
@@ -4041,14 +3949,12 @@ const environmentOption$3 = Options.text("environment").pipe(Options.withDefault
|
|
|
4041
3949
|
const importCommand = Command.make("import", {
|
|
4042
3950
|
file: fileArg,
|
|
4043
3951
|
environment: environmentOption$3
|
|
4044
|
-
}, ({ file
|
|
4045
|
-
const
|
|
4046
|
-
const content = yield* fs.readFileString(file$1);
|
|
3952
|
+
}, ({ file, environment }) => Effect.gen(function* () {
|
|
3953
|
+
const content = yield* (yield* FileSystem.FileSystem).readFileString(file);
|
|
4047
3954
|
const projectId = yield* readProjectId;
|
|
4048
|
-
const
|
|
4049
|
-
const result = yield* api["env-vars"].bulkImport({ payload: {
|
|
3955
|
+
const result = yield* (yield* apiClient)["env-vars"].bulkImport({ payload: {
|
|
4050
3956
|
projectId,
|
|
4051
|
-
environment
|
|
3957
|
+
environment,
|
|
4052
3958
|
content,
|
|
4053
3959
|
visibility: "plaintext"
|
|
4054
3960
|
} });
|
|
@@ -4058,10 +3964,10 @@ const importCommand = Command.make("import", {
|
|
|
4058
3964
|
//#endregion
|
|
4059
3965
|
//#region src/commands/env/list.ts
|
|
4060
3966
|
const environmentOption$2 = Options.text("environment").pipe(Options.optional);
|
|
4061
|
-
const listCommand$2 = Command.make("list", { environment: environmentOption$2 }, ({ environment
|
|
3967
|
+
const listCommand$2 = Command.make("list", { environment: environmentOption$2 }, ({ environment }) => Effect.gen(function* () {
|
|
4062
3968
|
const projectId = yield* readProjectId;
|
|
4063
3969
|
const api = yield* apiClient;
|
|
4064
|
-
const envFilter = Option.match(environment
|
|
3970
|
+
const envFilter = Option.match(environment, {
|
|
4065
3971
|
onNone: () => ({}),
|
|
4066
3972
|
onSome: (value) => ({ environment: value })
|
|
4067
3973
|
});
|
|
@@ -4089,12 +3995,11 @@ const listCommand$2 = Command.make("list", { environment: environmentOption$2 },
|
|
|
4089
3995
|
//#endregion
|
|
4090
3996
|
//#region src/commands/env/pull.ts
|
|
4091
3997
|
const environmentOption$1 = Options.text("environment").pipe(Options.withDefault("production"));
|
|
4092
|
-
const pullCommand = Command.make("pull", { environment: environmentOption$1 }, ({ environment
|
|
3998
|
+
const pullCommand = Command.make("pull", { environment: environmentOption$1 }, ({ environment }) => Effect.gen(function* () {
|
|
4093
3999
|
const projectId = yield* readProjectId;
|
|
4094
|
-
const
|
|
4095
|
-
const result = yield* api["env-vars"].export({ urlParams: {
|
|
4000
|
+
const result = yield* (yield* apiClient)["env-vars"].export({ urlParams: {
|
|
4096
4001
|
projectId,
|
|
4097
|
-
environment
|
|
4002
|
+
environment
|
|
4098
4003
|
} });
|
|
4099
4004
|
for (const item of result.items) {
|
|
4100
4005
|
const escaped = item.value.replaceAll("'", String.raw`'\''`);
|
|
@@ -4115,14 +4020,13 @@ const setCommand$1 = Command.make("set", {
|
|
|
4115
4020
|
keyValue,
|
|
4116
4021
|
environment: environmentOption,
|
|
4117
4022
|
visibility: visibilityOption
|
|
4118
|
-
}, ({ keyValue: { key, value }, environment
|
|
4023
|
+
}, ({ keyValue: { key, value }, environment, visibility }) => Effect.gen(function* () {
|
|
4119
4024
|
const projectId = yield* readProjectId;
|
|
4120
4025
|
const api = yield* apiClient;
|
|
4121
|
-
const
|
|
4026
|
+
const match = (yield* api["env-vars"].list({ urlParams: {
|
|
4122
4027
|
projectId,
|
|
4123
|
-
environment
|
|
4124
|
-
} });
|
|
4125
|
-
const match = existing.items.find((item) => item.key === key);
|
|
4028
|
+
environment
|
|
4029
|
+
} })).items.find((item) => item.key === key);
|
|
4126
4030
|
if (match) {
|
|
4127
4031
|
yield* api["env-vars"].update({
|
|
4128
4032
|
path: { id: match.id },
|
|
@@ -4131,16 +4035,16 @@ const setCommand$1 = Command.make("set", {
|
|
|
4131
4035
|
visibility
|
|
4132
4036
|
}
|
|
4133
4037
|
});
|
|
4134
|
-
yield* Console.log(`Updated ${key} in ${environment
|
|
4038
|
+
yield* Console.log(`Updated ${key} in ${environment}`);
|
|
4135
4039
|
} else {
|
|
4136
4040
|
yield* api["env-vars"].create({ payload: {
|
|
4137
4041
|
projectId,
|
|
4138
|
-
environment
|
|
4042
|
+
environment,
|
|
4139
4043
|
key,
|
|
4140
4044
|
value,
|
|
4141
4045
|
visibility
|
|
4142
4046
|
} });
|
|
4143
|
-
yield* Console.log(`Created ${key} in ${environment
|
|
4047
|
+
yield* Console.log(`Created ${key} in ${environment}`);
|
|
4144
4048
|
}
|
|
4145
4049
|
}).pipe(handleEnvCommandErrors));
|
|
4146
4050
|
|
|
@@ -4160,12 +4064,10 @@ const envCommand = Command.make("env", {}, () => Console.log("Manage environment
|
|
|
4160
4064
|
//#region src/commands/fingerprint/compare.ts
|
|
4161
4065
|
const hash = Args.text({ name: "hash" });
|
|
4162
4066
|
const compareCommand = Command.make("compare", { hash }, (opts) => Effect.gen(function* () {
|
|
4163
|
-
const
|
|
4164
|
-
const projectRoot = yield* runtime.cwd;
|
|
4165
|
-
const result = yield* runFingerprintFull(projectRoot);
|
|
4067
|
+
const result = yield* runFingerprintFull(yield* (yield* CliRuntime).cwd);
|
|
4166
4068
|
if (result.hash === opts.hash) {
|
|
4167
4069
|
yield* Console.log("Fingerprints match.");
|
|
4168
|
-
return
|
|
4070
|
+
return;
|
|
4169
4071
|
}
|
|
4170
4072
|
yield* Console.log("Fingerprints differ.");
|
|
4171
4073
|
yield* Console.log(` Local: ${result.hash}`);
|
|
@@ -4176,9 +4078,7 @@ const compareCommand = Command.make("compare", { hash }, (opts) => Effect.gen(fu
|
|
|
4176
4078
|
//#endregion
|
|
4177
4079
|
//#region src/commands/fingerprint/generate.ts
|
|
4178
4080
|
const generateCommand = Command.make("generate", {}, () => Effect.gen(function* () {
|
|
4179
|
-
const
|
|
4180
|
-
const projectRoot = yield* runtime.cwd;
|
|
4181
|
-
const result = yield* runFingerprintFull(projectRoot);
|
|
4081
|
+
const result = yield* runFingerprintFull(yield* (yield* CliRuntime).cwd);
|
|
4182
4082
|
yield* Console.log(result.hash);
|
|
4183
4083
|
if (result.sources.length > 0) yield* Console.log(`${result.sources.length} sources`);
|
|
4184
4084
|
}).pipe(Effect.catchTag("FingerprintError", (error) => exitWith(2, error.message))));
|
|
@@ -4190,11 +4090,10 @@ const fingerprintCommand = Command.make("fingerprint", {}, () => Console.log("Fi
|
|
|
4190
4090
|
//#endregion
|
|
4191
4091
|
//#region src/commands/init.ts
|
|
4192
4092
|
const initCommand = Command.make("init", {}, () => Effect.gen(function* () {
|
|
4193
|
-
const
|
|
4194
|
-
const
|
|
4195
|
-
const name$2 = asString$1(expo?.["name"]) ?? asString$1(expo?.["slug"]) ?? "untitled";
|
|
4093
|
+
const expo = asRecord((yield* readAppJson)["expo"]);
|
|
4094
|
+
const name = asString$1(expo?.["name"]) ?? asString$1(expo?.["slug"]) ?? "untitled";
|
|
4196
4095
|
const slug = yield* readSlug;
|
|
4197
|
-
yield* Console.log(`Linking project: ${name
|
|
4096
|
+
yield* Console.log(`Linking project: ${name} (${slug})`);
|
|
4198
4097
|
const api = yield* apiClient;
|
|
4199
4098
|
const { items } = yield* api.projects.list({ urlParams: {
|
|
4200
4099
|
page: 1,
|
|
@@ -4207,7 +4106,7 @@ const initCommand = Command.make("init", {}, () => Effect.gen(function* () {
|
|
|
4207
4106
|
} else {
|
|
4208
4107
|
yield* Console.log("No existing project found. Creating new project...");
|
|
4209
4108
|
const project = yield* api.projects.create({ payload: {
|
|
4210
|
-
name
|
|
4109
|
+
name,
|
|
4211
4110
|
slug
|
|
4212
4111
|
} });
|
|
4213
4112
|
yield* Console.log(`Created project: ${project.name} (${project.id})`);
|
|
@@ -4268,7 +4167,7 @@ const CALLBACK_PAGE = `<!doctype html>
|
|
|
4268
4167
|
render(error instanceof Error ? error.message : "Callback failed.");
|
|
4269
4168
|
});
|
|
4270
4169
|
}
|
|
4271
|
-
|
|
4170
|
+
<\/script>
|
|
4272
4171
|
</body>
|
|
4273
4172
|
</html>`;
|
|
4274
4173
|
const createBrowserLoginSession = (options = {}) => {
|
|
@@ -4336,8 +4235,7 @@ const writeFetchResponse = async (res, response) => {
|
|
|
4336
4235
|
const handleIncoming = async (req, res, session) => {
|
|
4337
4236
|
try {
|
|
4338
4237
|
const request = await toFetchRequest(req, "http://127.0.0.1");
|
|
4339
|
-
|
|
4340
|
-
await writeFetchResponse(res, response);
|
|
4238
|
+
await writeFetchResponse(res, await session.handleRequest(request));
|
|
4341
4239
|
} catch {
|
|
4342
4240
|
res.statusCode = 500;
|
|
4343
4241
|
res.end("Local callback failed");
|
|
@@ -4350,9 +4248,8 @@ const createBrowserLoginServer = (options = {}) => {
|
|
|
4350
4248
|
});
|
|
4351
4249
|
server.listen(0, "127.0.0.1");
|
|
4352
4250
|
const address = server.address();
|
|
4353
|
-
const port = address !== null && typeof address === "object" ? address.port : 0;
|
|
4354
4251
|
return {
|
|
4355
|
-
callbackUrl: `http://127.0.0.1:${port}${session.callbackPath}`,
|
|
4252
|
+
callbackUrl: `http://127.0.0.1:${address !== null && typeof address === "object" ? address.port : 0}${session.callbackPath}`,
|
|
4356
4253
|
waitForToken: session.waitForToken,
|
|
4357
4254
|
stop: () => {
|
|
4358
4255
|
session.dispose();
|
|
@@ -4364,23 +4261,21 @@ const createBrowserLoginServer = (options = {}) => {
|
|
|
4364
4261
|
//#endregion
|
|
4365
4262
|
//#region src/application/login.ts
|
|
4366
4263
|
const tokenPrompt = Prompt.password({ message: "Paste your API key (from dashboard > API Keys):" });
|
|
4367
|
-
const buildOpenBrowserCommand = (platform
|
|
4368
|
-
if (platform
|
|
4369
|
-
if (platform
|
|
4264
|
+
const buildOpenBrowserCommand = (platform, url) => {
|
|
4265
|
+
if (platform === "darwin") return Command$1.make("open", url);
|
|
4266
|
+
if (platform === "win32") return Command$1.make("cmd", "/c", "start", "", url);
|
|
4370
4267
|
return Command$1.make("xdg-open", url);
|
|
4371
4268
|
};
|
|
4372
4269
|
const openBrowser = (url) => Effect.gen(function* () {
|
|
4373
|
-
const
|
|
4374
|
-
|
|
4375
|
-
const opened = yield* Command$1.exitCode(command$1).pipe(Effect.map((code) => code === 0), Effect.catchAll(() => Effect.succeed(false)));
|
|
4376
|
-
if (!opened) yield* Console.log(`Open this URL manually:\n${url}`);
|
|
4270
|
+
const command = buildOpenBrowserCommand((yield* CliRuntime).platform, url);
|
|
4271
|
+
if (!(yield* Command$1.exitCode(command).pipe(Effect.map((code) => code === 0), Effect.catchAll(() => Effect.succeed(false))))) yield* Console.log(`Open this URL manually:\n${url}`);
|
|
4377
4272
|
});
|
|
4378
4273
|
const browserLogin = Effect.scoped(Effect.gen(function* () {
|
|
4379
4274
|
const configStore = yield* ConfigStore;
|
|
4380
4275
|
const authStore = yield* AuthStore;
|
|
4381
|
-
const
|
|
4276
|
+
const accountsUrl = yield* configStore.getAccountsUrl;
|
|
4382
4277
|
const loginServer = yield* Effect.acquireRelease(Effect.sync(createBrowserLoginServer), (server) => Effect.sync(server.stop));
|
|
4383
|
-
const loginUrl = `${
|
|
4278
|
+
const loginUrl = `${accountsUrl}/cli-login?callbackUrl=${encodeURIComponent(loginServer.callbackUrl)}`;
|
|
4384
4279
|
yield* Console.log("Opening browser for better-update login...");
|
|
4385
4280
|
yield* Console.log("");
|
|
4386
4281
|
yield* openBrowser(loginUrl);
|
|
@@ -4394,8 +4289,7 @@ const manualLogin = Effect.gen(function* () {
|
|
|
4394
4289
|
yield* Console.log("Get your API key from the dashboard > API Keys page");
|
|
4395
4290
|
yield* Console.log("");
|
|
4396
4291
|
const token = Redacted.value(yield* tokenPrompt);
|
|
4397
|
-
|
|
4398
|
-
yield* authStore.saveToken(token);
|
|
4292
|
+
yield* (yield* AuthStore).saveToken(token);
|
|
4399
4293
|
yield* Console.log("");
|
|
4400
4294
|
yield* Console.log("Logged in successfully. Token saved to ~/.better-update/auth.json");
|
|
4401
4295
|
});
|
|
@@ -4416,8 +4310,7 @@ const loginCommand = Command.make("login", { manualApiKey }, (opts) => runLogin(
|
|
|
4416
4310
|
//#endregion
|
|
4417
4311
|
//#region src/commands/logout.ts
|
|
4418
4312
|
const logoutCommand = Command.make("logout", {}, () => Effect.gen(function* () {
|
|
4419
|
-
|
|
4420
|
-
yield* authStore.clearToken;
|
|
4313
|
+
yield* (yield* AuthStore).clearToken;
|
|
4421
4314
|
yield* Console.log("Logged out. Auth token removed.");
|
|
4422
4315
|
}));
|
|
4423
4316
|
|
|
@@ -4428,8 +4321,7 @@ const idArg = Args.text({ name: "id" });
|
|
|
4428
4321
|
const nameOption = Options.text("name");
|
|
4429
4322
|
const slugOption = Options.text("slug");
|
|
4430
4323
|
const listCommand$1 = Command.make("list", {}, () => Effect.gen(function* () {
|
|
4431
|
-
const
|
|
4432
|
-
const { items } = yield* api.projects.list({ urlParams: {
|
|
4324
|
+
const { items } = yield* (yield* apiClient).projects.list({ urlParams: {
|
|
4433
4325
|
page: 1,
|
|
4434
4326
|
limit: 1e3
|
|
4435
4327
|
} });
|
|
@@ -4453,8 +4345,7 @@ const createCommand = Command.make("create", {
|
|
|
4453
4345
|
name: nameOption,
|
|
4454
4346
|
slug: slugOption
|
|
4455
4347
|
}, (opts) => Effect.gen(function* () {
|
|
4456
|
-
const
|
|
4457
|
-
const project = yield* api.projects.create({ payload: {
|
|
4348
|
+
const project = yield* (yield* apiClient).projects.create({ payload: {
|
|
4458
4349
|
name: opts.name,
|
|
4459
4350
|
slug: opts.slug
|
|
4460
4351
|
} });
|
|
@@ -4466,8 +4357,7 @@ const createCommand = Command.make("create", {
|
|
|
4466
4357
|
]);
|
|
4467
4358
|
}).pipe(handleErrors));
|
|
4468
4359
|
const getCommand = Command.make("get", { id: idArg }, (opts) => Effect.gen(function* () {
|
|
4469
|
-
const
|
|
4470
|
-
const project = yield* api.projects.get({ path: { id: opts.id } });
|
|
4360
|
+
const project = yield* (yield* apiClient).projects.get({ path: { id: opts.id } });
|
|
4471
4361
|
yield* printKeyValue([
|
|
4472
4362
|
["ID", project.id],
|
|
4473
4363
|
["Name", project.name],
|
|
@@ -4479,16 +4369,14 @@ const renameCommand = Command.make("rename", {
|
|
|
4479
4369
|
id: idArg,
|
|
4480
4370
|
name: nameOption
|
|
4481
4371
|
}, (opts) => Effect.gen(function* () {
|
|
4482
|
-
const
|
|
4483
|
-
const project = yield* api.projects.rename({
|
|
4372
|
+
const project = yield* (yield* apiClient).projects.rename({
|
|
4484
4373
|
path: { id: opts.id },
|
|
4485
4374
|
payload: { name: opts.name }
|
|
4486
4375
|
});
|
|
4487
4376
|
yield* Console.log(`Project renamed to "${project.name}".`);
|
|
4488
4377
|
}).pipe(handleErrors));
|
|
4489
4378
|
const deleteCommand$1 = Command.make("delete", { id: idArg }, (opts) => Effect.gen(function* () {
|
|
4490
|
-
|
|
4491
|
-
yield* api.projects.delete({ path: { id: opts.id } });
|
|
4379
|
+
yield* (yield* apiClient).projects.delete({ path: { id: opts.id } });
|
|
4492
4380
|
yield* Console.log(`Project ${opts.id} deleted.`);
|
|
4493
4381
|
}).pipe(handleErrors));
|
|
4494
4382
|
const projectsCommand = Command.make("projects", {}, () => Console.log("Manage projects. Run with --help for subcommands.")).pipe(Command.withSubcommands([
|
|
@@ -4543,14 +4431,13 @@ const handleUpdateCommandErrors = makeCommandErrorHandler({
|
|
|
4543
4431
|
UpdateRollbackError: 2,
|
|
4544
4432
|
UpdatePromoteError: 2
|
|
4545
4433
|
});
|
|
4546
|
-
const resolveNamedResourceId = (params) => resolveNamedResourceId$2(params, (message
|
|
4434
|
+
const resolveNamedResourceId = (params) => resolveNamedResourceId$2(params, (message) => new UpdateCommandError({ message }));
|
|
4547
4435
|
|
|
4548
4436
|
//#endregion
|
|
4549
4437
|
//#region src/commands/update/delete.ts
|
|
4550
4438
|
const groupId = Args.text({ name: "groupId" });
|
|
4551
4439
|
const deleteCommand = Command.make("delete", { groupId }, (opts) => Effect.gen(function* () {
|
|
4552
|
-
const
|
|
4553
|
-
const result = yield* api.updates.deleteGroup({ path: { groupId: opts.groupId } });
|
|
4440
|
+
const result = yield* (yield* apiClient).updates.deleteGroup({ path: { groupId: opts.groupId } });
|
|
4554
4441
|
yield* Console.log(`Deleted ${String(result.deleted)} update(s) from group ${opts.groupId}.`);
|
|
4555
4442
|
}).pipe(handleUpdateCommandErrors));
|
|
4556
4443
|
|
|
@@ -4639,30 +4526,30 @@ const loadSignedPayloadFromFiles = (params) => Effect.gen(function* () {
|
|
|
4639
4526
|
const loadOptionalSignedPayload = loadSignedPayloadFromFiles;
|
|
4640
4527
|
const loadSignedPublishPayloads = (params) => Effect.gen(function* () {
|
|
4641
4528
|
const targetedPlatforms = new Set(params.platforms);
|
|
4642
|
-
const nonTargetedPlatforms = ["ios", "android"].filter((platform
|
|
4529
|
+
const nonTargetedPlatforms = ["ios", "android"].filter((platform) => !targetedPlatforms.has(platform) && hasAnySignedPayloadFile(params.platformFiles[platform] ?? emptySignedPayloadFileSet));
|
|
4643
4530
|
if (nonTargetedPlatforms.length > 0) return yield* Effect.fail(params.makeError(`Signed publish inputs were provided for non-targeted platform(s): ${nonTargetedPlatforms.join(", ")}.`));
|
|
4644
4531
|
const hasGlobalFiles = hasAnySignedPayloadFile(params.globalFiles);
|
|
4645
4532
|
if (!hasGlobalFiles && Object.values(params.platformFiles).every((files) => !hasAnySignedPayloadFile(files))) return {};
|
|
4646
4533
|
if (params.platforms.length > 1 && hasGlobalFiles) return yield* Effect.fail(params.makeError("Signed multi-platform publish requires per-platform file sets. Use the --*-ios and --*-android options."));
|
|
4647
4534
|
if (params.platforms.length === 1 && hasGlobalFiles) {
|
|
4648
|
-
const [platform
|
|
4649
|
-
if (!platform
|
|
4650
|
-
if (hasAnySignedPayloadFile(params.platformFiles[platform
|
|
4535
|
+
const [platform] = params.platforms;
|
|
4536
|
+
if (!platform) return {};
|
|
4537
|
+
if (hasAnySignedPayloadFile(params.platformFiles[platform] ?? emptySignedPayloadFileSet)) return yield* Effect.fail(params.makeError(`Signed publish for ${platform} is ambiguous. Use either the generic file options or the ${platform}-specific file options, not both.`));
|
|
4651
4538
|
const globalPayload = yield* loadSignedPayloadFromFiles({
|
|
4652
4539
|
files: params.globalFiles,
|
|
4653
4540
|
label: "Signed publish",
|
|
4654
4541
|
makeError: params.makeError
|
|
4655
4542
|
});
|
|
4656
|
-
return globalPayload === null ? {} : { [platform
|
|
4543
|
+
return globalPayload === null ? {} : { [platform]: globalPayload };
|
|
4657
4544
|
}
|
|
4658
|
-
const platformPayloadEntries = yield* Effect.forEach(params.platforms, (platform
|
|
4545
|
+
const platformPayloadEntries = yield* Effect.forEach(params.platforms, (platform) => Effect.gen(function* () {
|
|
4659
4546
|
const payload = yield* loadSignedPayloadFromFiles({
|
|
4660
|
-
files: params.platformFiles[platform
|
|
4661
|
-
label: `Signed publish for ${platform
|
|
4547
|
+
files: params.platformFiles[platform] ?? emptySignedPayloadFileSet,
|
|
4548
|
+
label: `Signed publish for ${platform}`,
|
|
4662
4549
|
makeError: params.makeError
|
|
4663
4550
|
});
|
|
4664
|
-
if (payload === null) return yield* Effect.fail(params.makeError(`Signed multi-platform publish requires a signed payload for ${platform
|
|
4665
|
-
return [platform
|
|
4551
|
+
if (payload === null) return yield* Effect.fail(params.makeError(`Signed multi-platform publish requires a signed payload for ${platform}.`));
|
|
4552
|
+
return [platform, payload];
|
|
4666
4553
|
}), { concurrency: 1 });
|
|
4667
4554
|
return Object.fromEntries(platformPayloadEntries);
|
|
4668
4555
|
});
|
|
@@ -4678,9 +4565,9 @@ const runUpdatePromote = (options) => Effect.gen(function* () {
|
|
|
4678
4565
|
certificateChainFile: options.certificateChainFile
|
|
4679
4566
|
},
|
|
4680
4567
|
label: "Signed promote",
|
|
4681
|
-
makeError: (message
|
|
4568
|
+
makeError: (message) => new UpdatePromoteError({ message })
|
|
4682
4569
|
});
|
|
4683
|
-
const
|
|
4570
|
+
const [promotedUpdate] = (yield* api.updates.republish({ payload: {
|
|
4684
4571
|
sourceUpdateId: options.updateId,
|
|
4685
4572
|
destinationChannel: options.channel,
|
|
4686
4573
|
...signedPayload ? { signedUpdates: [{
|
|
@@ -4689,8 +4576,7 @@ const runUpdatePromote = (options) => Effect.gen(function* () {
|
|
|
4689
4576
|
signature: signedPayload.signature,
|
|
4690
4577
|
certificateChain: signedPayload.certificateChain
|
|
4691
4578
|
}] } : {}
|
|
4692
|
-
} }).pipe(Effect.catchIf((cause) => cause._tag !== "AuthRequiredError", (cause) => new UpdatePromoteError({ message: `Failed to promote update: ${formatCause(cause)}` })));
|
|
4693
|
-
const [promotedUpdate] = result.updates;
|
|
4579
|
+
} }).pipe(Effect.catchIf((cause) => cause._tag !== "AuthRequiredError", (cause) => new UpdatePromoteError({ message: `Failed to promote update: ${formatCause(cause)}` })))).updates;
|
|
4694
4580
|
if (!promotedUpdate) return yield* new UpdatePromoteError({ message: "Promote completed without returning a promoted update." });
|
|
4695
4581
|
return {
|
|
4696
4582
|
sourceUpdateId: options.updateId,
|
|
@@ -4727,7 +4613,7 @@ const promoteCommand = Command.make("promote", {
|
|
|
4727
4613
|
//#region src/lib/expo-export.ts
|
|
4728
4614
|
const asString = (value) => typeof value === "string" ? value : void 0;
|
|
4729
4615
|
const normalizeExtension = (value) => {
|
|
4730
|
-
if (!value) return
|
|
4616
|
+
if (!value) return;
|
|
4731
4617
|
return value.startsWith(".") ? value.slice(1) : value;
|
|
4732
4618
|
};
|
|
4733
4619
|
const inferContentType = (fileExt, isLaunch) => {
|
|
@@ -4762,43 +4648,39 @@ const runCommand = (cmd, step) => Command$1.exitCode(cmd.pipe(Command$1.stdout("
|
|
|
4762
4648
|
message: `${step} exited with code ${code}`
|
|
4763
4649
|
}))));
|
|
4764
4650
|
const readExpoPublicConfig = ({ projectRoot, envVars }) => Effect.gen(function* () {
|
|
4765
|
-
const
|
|
4766
|
-
const commandEnv = yield* runtime.commandEnvironment(envVars);
|
|
4651
|
+
const commandEnv = yield* (yield* CliRuntime).commandEnvironment(envVars);
|
|
4767
4652
|
const stdout = yield* Command$1.string(makeBunxCommand("expo", "config", "--type", "public", "--json").pipe(Command$1.workingDirectory(projectRoot), Command$1.env(commandEnv))).pipe(Effect.mapError((cause) => new UpdatePublishError({ message: `Failed to read Expo public config: ${String(cause)}` })));
|
|
4768
|
-
const
|
|
4653
|
+
const config = asRecord(yield* Effect.try({
|
|
4769
4654
|
try: () => JSON.parse(stdout),
|
|
4770
4655
|
catch: () => new UpdatePublishError({ message: "Expo public config output was not valid JSON." })
|
|
4771
|
-
});
|
|
4772
|
-
const config = asRecord(parsed);
|
|
4656
|
+
}));
|
|
4773
4657
|
if (!config) return yield* new UpdatePublishError({ message: "Expo public config did not decode to a JSON object." });
|
|
4774
4658
|
return config;
|
|
4775
4659
|
});
|
|
4776
|
-
const runExpoExport = ({ projectRoot, exportDir, platform
|
|
4777
|
-
const
|
|
4778
|
-
const commandEnv = yield* runtime.commandEnvironment(envVars);
|
|
4660
|
+
const runExpoExport = ({ projectRoot, exportDir, platform, envVars, clear }) => Effect.gen(function* () {
|
|
4661
|
+
const commandEnv = yield* (yield* CliRuntime).commandEnvironment(envVars);
|
|
4779
4662
|
const args = [
|
|
4780
4663
|
"expo",
|
|
4781
4664
|
"export",
|
|
4782
4665
|
"--platform",
|
|
4783
|
-
platform
|
|
4666
|
+
platform,
|
|
4784
4667
|
"--output-dir",
|
|
4785
4668
|
exportDir,
|
|
4786
4669
|
"--dump-assetmap"
|
|
4787
4670
|
];
|
|
4788
|
-
if (clear
|
|
4789
|
-
return yield* runCommand(makeBunxCommand(...args).pipe(Command$1.workingDirectory(projectRoot), Command$1.env(commandEnv)), `expo export ${platform
|
|
4671
|
+
if (clear) args.push("--clear");
|
|
4672
|
+
return yield* runCommand(makeBunxCommand(...args).pipe(Command$1.workingDirectory(projectRoot), Command$1.env(commandEnv)), `expo export ${platform}`);
|
|
4790
4673
|
});
|
|
4791
|
-
const readExpoExportAssets = ({ exportDir, platform
|
|
4674
|
+
const readExpoExportAssets = ({ exportDir, platform }) => Effect.gen(function* () {
|
|
4792
4675
|
const fs = yield* FileSystem.FileSystem;
|
|
4793
4676
|
const metadataPath = path.join(exportDir, "metadata.json");
|
|
4794
4677
|
const metadataText = yield* fs.readFileString(metadataPath).pipe(Effect.mapError(() => new UpdatePublishError({ message: `Expected Expo export metadata at ${metadataPath}.` })));
|
|
4795
|
-
const
|
|
4678
|
+
const platformMetadata = asRecord(asRecord(asRecord(yield* Effect.try({
|
|
4796
4679
|
try: () => JSON.parse(metadataText),
|
|
4797
4680
|
catch: () => new UpdatePublishError({ message: `Failed to parse ${metadataPath} as JSON.` })
|
|
4798
|
-
});
|
|
4799
|
-
const platformMetadata = asRecord(asRecord(asRecord(metadata)?.["fileMetadata"])?.[platform$7]);
|
|
4681
|
+
}))?.["fileMetadata"])?.[platform]);
|
|
4800
4682
|
const bundlePath = asString(platformMetadata?.["bundle"]);
|
|
4801
|
-
if (!bundlePath) return yield* new UpdatePublishError({ message: `Expo export did not contain a bundle path for platform "${platform
|
|
4683
|
+
if (!bundlePath) return yield* new UpdatePublishError({ message: `Expo export did not contain a bundle path for platform "${platform}".` });
|
|
4802
4684
|
const bundleExt = normalizeExtension(path.extname(bundlePath)) ?? "js";
|
|
4803
4685
|
const rawAssets = Array.isArray(platformMetadata?.["assets"]) ? platformMetadata["assets"] : [];
|
|
4804
4686
|
const assets = yield* Effect.forEach(rawAssets, (rawAsset, index) => Effect.gen(function* () {
|
|
@@ -4828,21 +4710,21 @@ const readExpoExportAssets = ({ exportDir, platform: platform$7 }) => Effect.gen
|
|
|
4828
4710
|
const resolveUpdatePlatforms = (appJson, requestedPlatform) => {
|
|
4829
4711
|
if (requestedPlatform !== "all") return [requestedPlatform];
|
|
4830
4712
|
const expo = asRecord(appJson["expo"]);
|
|
4831
|
-
return ["ios", "android"].filter((platform
|
|
4713
|
+
return ["ios", "android"].filter((platform) => asRecord(expo?.[platform]) !== void 0);
|
|
4832
4714
|
};
|
|
4833
4715
|
|
|
4834
4716
|
//#endregion
|
|
4835
4717
|
//#region src/application/update-publish.ts
|
|
4836
|
-
const buildUpdateExtra = (expoClient, projectId, environment
|
|
4718
|
+
const buildUpdateExtra = (expoClient, projectId, environment) => ({
|
|
4837
4719
|
expoClient,
|
|
4838
4720
|
eas: { projectId },
|
|
4839
|
-
environment
|
|
4721
|
+
environment
|
|
4840
4722
|
});
|
|
4841
4723
|
const dedupeAssetsByHash = (assets) => uniqBy(assets, (asset) => asset.hash);
|
|
4842
|
-
const preparePlatformAssets = ({ exportDir, platform
|
|
4724
|
+
const preparePlatformAssets = ({ exportDir, platform }) => Effect.gen(function* () {
|
|
4843
4725
|
const exportedAssets = yield* readExpoExportAssets({
|
|
4844
4726
|
exportDir,
|
|
4845
|
-
platform
|
|
4727
|
+
platform
|
|
4846
4728
|
});
|
|
4847
4729
|
return yield* Effect.forEach(exportedAssets, (asset) => sha256File(asset.path).pipe(Effect.map(({ sha256: contentSha256Hex, byteSize }) => ({
|
|
4848
4730
|
...asset,
|
|
@@ -4924,85 +4806,80 @@ const publishPlatform = (params) => Effect.gen(function* () {
|
|
|
4924
4806
|
deduplicatedAssets: assetRegistration.deduplicated.length
|
|
4925
4807
|
};
|
|
4926
4808
|
});
|
|
4927
|
-
const runUpdatePublish = (options) => Effect.scoped(
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
|
|
4933
|
-
|
|
4934
|
-
|
|
4935
|
-
|
|
4936
|
-
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
if (!resolvedBranch) {
|
|
4952
|
-
if (!gitContext.ref) return yield* new UpdatePublishError({ message: "Cannot infer branch from git. Ensure you are in a git repo with a checked-out branch, or provide --branch explicitly." });
|
|
4953
|
-
resolvedBranch = gitContext.ref;
|
|
4954
|
-
}
|
|
4955
|
-
if (!resolvedMessage && gitContext.commitMessage) resolvedMessage = gitContext.commitMessage;
|
|
4809
|
+
const runUpdatePublish = (options) => Effect.scoped(Effect.gen(function* () {
|
|
4810
|
+
const projectRoot = yield* (yield* CliRuntime).cwd;
|
|
4811
|
+
const api = yield* apiClient;
|
|
4812
|
+
const projectId = yield* readProjectId;
|
|
4813
|
+
const slug = yield* readSlug;
|
|
4814
|
+
const appJson = yield* readAppJson;
|
|
4815
|
+
const platforms = resolveUpdatePlatforms(appJson, options.platform);
|
|
4816
|
+
if (platforms.length === 0) return yield* new UpdatePublishError({ message: "No publishable platforms found in app.json. Add an \"expo.ios\" or \"expo.android\" section, or pass --platform explicitly." });
|
|
4817
|
+
const environmentVars = yield* pullEnvVars(api, {
|
|
4818
|
+
projectId,
|
|
4819
|
+
environment: options.environment
|
|
4820
|
+
});
|
|
4821
|
+
const expoClientConfig = yield* readExpoPublicConfig({
|
|
4822
|
+
projectRoot,
|
|
4823
|
+
envVars: environmentVars
|
|
4824
|
+
});
|
|
4825
|
+
const tempDir = yield* acquireBuildTempDir.pipe(Effect.mapError((cause) => new UpdatePublishError({ message: `Failed to create a temporary export directory: ${formatCause(cause)}` })));
|
|
4826
|
+
let resolvedBranch = options.branch;
|
|
4827
|
+
let resolvedMessage = options.message;
|
|
4828
|
+
if (options.auto) {
|
|
4829
|
+
const gitContext = yield* readGitContext(projectRoot);
|
|
4830
|
+
if (!resolvedBranch) {
|
|
4831
|
+
if (!gitContext.ref) return yield* new UpdatePublishError({ message: "Cannot infer branch from git. Ensure you are in a git repo with a checked-out branch, or provide --branch explicitly." });
|
|
4832
|
+
resolvedBranch = gitContext.ref;
|
|
4956
4833
|
}
|
|
4957
|
-
if (!
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
4964
|
-
|
|
4965
|
-
|
|
4966
|
-
|
|
4967
|
-
|
|
4968
|
-
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
manifestBodyFile: options.manifestBodyFileAndroid,
|
|
4976
|
-
signatureFile: options.signatureFileAndroid,
|
|
4977
|
-
certificateChainFile: options.certificateChainFileAndroid
|
|
4978
|
-
}
|
|
4834
|
+
if (!resolvedMessage && gitContext.commitMessage) resolvedMessage = gitContext.commitMessage;
|
|
4835
|
+
}
|
|
4836
|
+
if (!resolvedBranch) return yield* new UpdatePublishError({ message: "Missing --branch. Provide it explicitly or use --auto to infer from git." });
|
|
4837
|
+
const branch = resolvedBranch;
|
|
4838
|
+
const groupId = randomUUID();
|
|
4839
|
+
const message = resolvedMessage ?? "Publish via better-update CLI";
|
|
4840
|
+
const signedPayloads = yield* loadSignedPublishPayloads({
|
|
4841
|
+
platforms,
|
|
4842
|
+
globalFiles: {
|
|
4843
|
+
manifestBodyFile: options.manifestBodyFile,
|
|
4844
|
+
signatureFile: options.signatureFile,
|
|
4845
|
+
certificateChainFile: options.certificateChainFile
|
|
4846
|
+
},
|
|
4847
|
+
platformFiles: {
|
|
4848
|
+
ios: {
|
|
4849
|
+
manifestBodyFile: options.manifestBodyFileIos,
|
|
4850
|
+
signatureFile: options.signatureFileIos,
|
|
4851
|
+
certificateChainFile: options.certificateChainFileIos
|
|
4979
4852
|
},
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4853
|
+
android: {
|
|
4854
|
+
manifestBodyFile: options.manifestBodyFileAndroid,
|
|
4855
|
+
signatureFile: options.signatureFileAndroid,
|
|
4856
|
+
certificateChainFile: options.certificateChainFileAndroid
|
|
4857
|
+
}
|
|
4858
|
+
},
|
|
4859
|
+
makeError: (errorMessage) => new UpdatePublishError({ message: errorMessage })
|
|
4860
|
+
});
|
|
4861
|
+
return {
|
|
4862
|
+
groupId,
|
|
4863
|
+
branch,
|
|
4864
|
+
results: yield* Effect.forEach(platforms, (platform) => publishPlatform({
|
|
4983
4865
|
projectRoot,
|
|
4984
|
-
exportDir: path.join(tempDir, `export-${platform
|
|
4866
|
+
exportDir: path.join(tempDir, `export-${platform}`),
|
|
4985
4867
|
projectId,
|
|
4986
4868
|
slug,
|
|
4987
|
-
branch
|
|
4988
|
-
groupId
|
|
4989
|
-
message
|
|
4869
|
+
branch,
|
|
4870
|
+
groupId,
|
|
4871
|
+
message,
|
|
4990
4872
|
environment: options.environment,
|
|
4991
4873
|
environmentVars,
|
|
4992
4874
|
expoClientConfig,
|
|
4993
4875
|
clear: options.clear,
|
|
4994
4876
|
appJson,
|
|
4995
|
-
platform
|
|
4996
|
-
signedPayload: signedPayloads[platform
|
|
4877
|
+
platform,
|
|
4878
|
+
signedPayload: signedPayloads[platform] ?? null,
|
|
4997
4879
|
rolloutPercentage: options.rolloutPercentage
|
|
4998
|
-
}), { concurrency: 1 })
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
branch: branch$6,
|
|
5002
|
-
results
|
|
5003
|
-
};
|
|
5004
|
-
})
|
|
5005
|
-
);
|
|
4880
|
+
}), { concurrency: 1 })
|
|
4881
|
+
};
|
|
4882
|
+
}));
|
|
5006
4883
|
|
|
5007
4884
|
//#endregion
|
|
5008
4885
|
//#region src/commands/update/publish.ts
|
|
@@ -5089,17 +4966,17 @@ const publishCommand = Command.make("publish", {
|
|
|
5089
4966
|
|
|
5090
4967
|
//#endregion
|
|
5091
4968
|
//#region ../../packages/expo-protocol/src/index.ts
|
|
5092
|
-
const buildRollbackDirectiveBody = (commitTime
|
|
4969
|
+
const buildRollbackDirectiveBody = (commitTime) => JSON.stringify({
|
|
5093
4970
|
type: "rollBackToEmbedded",
|
|
5094
|
-
parameters: { commitTime
|
|
4971
|
+
parameters: { commitTime }
|
|
5095
4972
|
});
|
|
5096
4973
|
|
|
5097
4974
|
//#endregion
|
|
5098
4975
|
//#region src/application/update-rollback.ts
|
|
5099
4976
|
const resolveCommitTime = (input) => Effect.gen(function* () {
|
|
5100
|
-
const commitTime
|
|
5101
|
-
if (Number.isNaN(Date.parse(commitTime
|
|
5102
|
-
return commitTime
|
|
4977
|
+
const commitTime = input ?? (/* @__PURE__ */ new Date()).toISOString();
|
|
4978
|
+
if (Number.isNaN(Date.parse(commitTime))) return yield* new UpdateRollbackError({ message: "commitTime must be a valid ISO 8601 timestamp." });
|
|
4979
|
+
return commitTime;
|
|
5103
4980
|
});
|
|
5104
4981
|
const extractDirectiveCommitTime = (directiveBody) => Effect.gen(function* () {
|
|
5105
4982
|
const directive = yield* Effect.try({
|
|
@@ -5110,14 +4987,13 @@ const extractDirectiveCommitTime = (directiveBody) => Effect.gen(function* () {
|
|
|
5110
4987
|
if (directive["type"] !== "rollBackToEmbedded") return yield* new UpdateRollbackError({ message: "directiveBody.type must be \"rollBackToEmbedded\"." });
|
|
5111
4988
|
const { parameters } = directive;
|
|
5112
4989
|
if (!isRecord(parameters)) return yield* new UpdateRollbackError({ message: "directiveBody.parameters must be an object." });
|
|
5113
|
-
const { commitTime
|
|
5114
|
-
if (typeof commitTime
|
|
5115
|
-
return commitTime
|
|
4990
|
+
const { commitTime } = parameters;
|
|
4991
|
+
if (typeof commitTime !== "string" || Number.isNaN(Date.parse(commitTime))) return yield* new UpdateRollbackError({ message: "directiveBody.parameters.commitTime must be a valid ISO 8601 timestamp." });
|
|
4992
|
+
return commitTime;
|
|
5116
4993
|
});
|
|
5117
4994
|
const loadOptionalSignedRollbackPayload = (options) => Effect.gen(function* () {
|
|
5118
4995
|
const fileSystem = yield* FileSystem.FileSystem;
|
|
5119
|
-
|
|
5120
|
-
if (!hasAnySigningInput) return null;
|
|
4996
|
+
if (!(options.directiveBodyFile !== void 0 || options.signatureFile !== void 0 || options.certificateChainFile !== void 0)) return null;
|
|
5121
4997
|
if (!options.directiveBodyFile || !options.signatureFile || !options.certificateChainFile) return yield* new UpdateRollbackError({ message: "Signed rollback requires --directive-body-file, --signature-file, and --certificate-chain-file together." });
|
|
5122
4998
|
const [directiveBody, signature, certificateChain] = yield* Effect.all([
|
|
5123
4999
|
fileSystem.readFileString(options.directiveBodyFile),
|
|
@@ -5131,8 +5007,7 @@ const loadOptionalSignedRollbackPayload = (options) => Effect.gen(function* () {
|
|
|
5131
5007
|
};
|
|
5132
5008
|
});
|
|
5133
5009
|
const createRollbackForPlatform = (params) => Effect.gen(function* () {
|
|
5134
|
-
const
|
|
5135
|
-
const update = yield* api.updates.create({ payload: {
|
|
5010
|
+
const update = yield* (yield* apiClient).updates.create({ payload: {
|
|
5136
5011
|
branch: params.branch,
|
|
5137
5012
|
slug: params.projectSlug,
|
|
5138
5013
|
runtimeVersion: params.runtimeVersion,
|
|
@@ -5153,8 +5028,7 @@ const createRollbackForPlatform = (params) => Effect.gen(function* () {
|
|
|
5153
5028
|
};
|
|
5154
5029
|
});
|
|
5155
5030
|
const runUpdateRollback = (options) => Effect.gen(function* () {
|
|
5156
|
-
const
|
|
5157
|
-
const projectRoot = yield* runtime.cwd;
|
|
5031
|
+
const projectRoot = yield* (yield* CliRuntime).cwd;
|
|
5158
5032
|
yield* readProjectId;
|
|
5159
5033
|
const projectSlug = yield* readSlug;
|
|
5160
5034
|
const appJson = yield* readAppJson;
|
|
@@ -5167,28 +5041,28 @@ const runUpdateRollback = (options) => Effect.gen(function* () {
|
|
|
5167
5041
|
projectRoot
|
|
5168
5042
|
});
|
|
5169
5043
|
const signedPayload = yield* loadOptionalSignedRollbackPayload(options);
|
|
5170
|
-
const commitTime
|
|
5044
|
+
const commitTime = signedPayload ? yield* Effect.gen(function* () {
|
|
5171
5045
|
const directiveCommitTime = yield* extractDirectiveCommitTime(signedPayload.directiveBody);
|
|
5172
5046
|
if (options.commitTime && options.commitTime !== directiveCommitTime) return yield* new UpdateRollbackError({ message: "commitTime must match directiveBody.parameters.commitTime in signed mode." });
|
|
5173
5047
|
return directiveCommitTime;
|
|
5174
5048
|
}) : yield* resolveCommitTime(options.commitTime);
|
|
5175
|
-
const groupId
|
|
5176
|
-
const message
|
|
5177
|
-
const results = yield* Effect.forEach(platforms, (platform
|
|
5049
|
+
const groupId = randomUUID();
|
|
5050
|
+
const message = options.message ?? "Rollback to embedded via better-update CLI";
|
|
5051
|
+
const results = yield* Effect.forEach(platforms, (platform) => createRollbackForPlatform({
|
|
5178
5052
|
branch: options.branch,
|
|
5179
5053
|
projectSlug,
|
|
5180
5054
|
runtimeVersion,
|
|
5181
|
-
platform
|
|
5182
|
-
message
|
|
5183
|
-
groupId
|
|
5184
|
-
directiveBody: signedPayload?.directiveBody ?? buildRollbackDirectiveBody(commitTime
|
|
5055
|
+
platform,
|
|
5056
|
+
message,
|
|
5057
|
+
groupId,
|
|
5058
|
+
directiveBody: signedPayload?.directiveBody ?? buildRollbackDirectiveBody(commitTime),
|
|
5185
5059
|
signature: signedPayload?.signature,
|
|
5186
5060
|
certificateChain: signedPayload?.certificateChain
|
|
5187
5061
|
}), { concurrency: 1 });
|
|
5188
5062
|
return {
|
|
5189
|
-
groupId
|
|
5063
|
+
groupId,
|
|
5190
5064
|
branch: options.branch,
|
|
5191
|
-
commitTime
|
|
5065
|
+
commitTime,
|
|
5192
5066
|
results
|
|
5193
5067
|
};
|
|
5194
5068
|
});
|
|
@@ -5241,8 +5115,7 @@ const rollbackCommand = Command.make("rollback", {
|
|
|
5241
5115
|
//#region src/commands/update/rollout/complete.ts
|
|
5242
5116
|
const updateId$2 = Args.text({ name: "updateId" });
|
|
5243
5117
|
const completeCommand = Command.make("complete", { updateId: updateId$2 }, (opts) => Effect.gen(function* () {
|
|
5244
|
-
const
|
|
5245
|
-
const result = yield* api.updates.completeRollout({ path: { id: opts.updateId } });
|
|
5118
|
+
const result = yield* (yield* apiClient).updates.completeRollout({ path: { id: opts.updateId } });
|
|
5246
5119
|
yield* Console.log(`Completed rollout for ${opts.updateId}. Current rollout is ${String(result.rolloutPercentage)}%.`);
|
|
5247
5120
|
}).pipe(handleUpdateCommandErrors));
|
|
5248
5121
|
|
|
@@ -5250,8 +5123,7 @@ const completeCommand = Command.make("complete", { updateId: updateId$2 }, (opts
|
|
|
5250
5123
|
//#region src/commands/update/rollout/revert.ts
|
|
5251
5124
|
const updateId$1 = Args.text({ name: "updateId" });
|
|
5252
5125
|
const revertCommand = Command.make("revert", { updateId: updateId$1 }, (opts) => Effect.gen(function* () {
|
|
5253
|
-
const
|
|
5254
|
-
const result = yield* api.updates.revertRollout({ path: { id: opts.updateId } });
|
|
5126
|
+
const result = yield* (yield* apiClient).updates.revertRollout({ path: { id: opts.updateId } });
|
|
5255
5127
|
yield* Console.log(`Reverted rollout for ${opts.updateId}. Current rollout is ${String(result.rolloutPercentage)}%.`);
|
|
5256
5128
|
}).pipe(handleUpdateCommandErrors));
|
|
5257
5129
|
|
|
@@ -5263,8 +5135,7 @@ const setCommand = Command.make("set", {
|
|
|
5263
5135
|
updateId,
|
|
5264
5136
|
percentage
|
|
5265
5137
|
}, (opts) => Effect.gen(function* () {
|
|
5266
|
-
const
|
|
5267
|
-
const result = yield* api.updates.editRollout({
|
|
5138
|
+
const result = yield* (yield* apiClient).updates.editRollout({
|
|
5268
5139
|
path: { id: opts.updateId },
|
|
5269
5140
|
payload: { percentage: opts.percentage }
|
|
5270
5141
|
});
|
|
@@ -5309,11 +5180,11 @@ const command = Command.make("better-update", {}, () => Console.log("better-upda
|
|
|
5309
5180
|
analyticsCommand,
|
|
5310
5181
|
auditLogsCommand
|
|
5311
5182
|
]));
|
|
5312
|
-
|
|
5183
|
+
Command.run(command, {
|
|
5313
5184
|
name: "better-update",
|
|
5314
5185
|
version: "0.1.0"
|
|
5315
|
-
});
|
|
5316
|
-
cli(process.argv).pipe(Effect.provide(CliLive), NodeRuntime.runMain);
|
|
5186
|
+
})(process.argv).pipe(Effect.provide(CliLive), NodeRuntime.runMain);
|
|
5317
5187
|
|
|
5318
5188
|
//#endregion
|
|
5319
|
-
|
|
5189
|
+
export { };
|
|
5190
|
+
//# sourceMappingURL=index.mjs.map
|