@better-update/cli 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +1342 -131
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -17,11 +17,12 @@ import forge from "node-forge";
|
|
|
17
17
|
import { accessSync, chmodSync, constants, createReadStream, existsSync, promises, readFileSync, writeFileSync } from "node:fs";
|
|
18
18
|
import { spawn as spawn$1 } from "node-pty";
|
|
19
19
|
import chalk from "chalk";
|
|
20
|
-
import os from "node:os";
|
|
20
|
+
import os, { tmpdir } from "node:os";
|
|
21
21
|
import plistMod from "@expo/plist";
|
|
22
22
|
import { ExpoRunFormatter } from "@expo/xcpretty";
|
|
23
23
|
import { promisify } from "node:util";
|
|
24
24
|
import ignore from "ignore";
|
|
25
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
25
26
|
import { Buffer as Buffer$1 } from "node:buffer";
|
|
26
27
|
import { getFormattedSerialNumber, getX509Certificate, parsePKCS12 } from "@expo/pkcs12";
|
|
27
28
|
import qrcode from "qrcode-terminal";
|
|
@@ -32,7 +33,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
32
33
|
|
|
33
34
|
//#endregion
|
|
34
35
|
//#region package.json
|
|
35
|
-
var version = "0.
|
|
36
|
+
var version = "0.20.0";
|
|
36
37
|
|
|
37
38
|
//#endregion
|
|
38
39
|
//#region src/lib/interactive-mode.ts
|
|
@@ -200,15 +201,15 @@ var NotAcceptable = class extends Schema.TaggedError()("NotAcceptable", { messag
|
|
|
200
201
|
|
|
201
202
|
//#endregion
|
|
202
203
|
//#region ../../packages/api/src/groups/android-application-identifiers.ts
|
|
203
|
-
const idParam$
|
|
204
|
-
const projectIdParam$
|
|
205
|
-
var AndroidApplicationIdentifiersGroup = class extends HttpApiGroup.make("androidApplicationIdentifiers").add(HttpApiEndpoint.get("list")`/api/projects/${projectIdParam$
|
|
204
|
+
const idParam$18 = HttpApiSchema.param("id", Schema.String);
|
|
205
|
+
const projectIdParam$6 = HttpApiSchema.param("projectId", Schema.String);
|
|
206
|
+
var AndroidApplicationIdentifiersGroup = class extends HttpApiGroup.make("androidApplicationIdentifiers").add(HttpApiEndpoint.get("list")`/api/projects/${projectIdParam$6}/android-application-identifiers`.addSuccess(Schema.Struct({ items: Schema.Array(AndroidApplicationIdentifier) })).annotateContext(OpenApi.annotations({
|
|
206
207
|
title: "List Android application identifiers",
|
|
207
208
|
description: "List all Android package identifiers for a project"
|
|
208
|
-
}))).add(HttpApiEndpoint.post("create")`/api/projects/${projectIdParam$
|
|
209
|
+
}))).add(HttpApiEndpoint.post("create")`/api/projects/${projectIdParam$6}/android-application-identifiers`.setPayload(CreateAndroidApplicationIdentifierBody).addSuccess(AndroidApplicationIdentifier, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
209
210
|
title: "Create Android application identifier",
|
|
210
211
|
description: "Register an Android package name for a project"
|
|
211
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/android-application-identifiers/${idParam$
|
|
212
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/android-application-identifiers/${idParam$18}`.addSuccess(DeleteAndroidApplicationIdentifierResult).annotateContext(OpenApi.annotations({
|
|
212
213
|
title: "Delete Android application identifier",
|
|
213
214
|
description: "Remove an Android application identifier"
|
|
214
215
|
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -248,7 +249,7 @@ const DeleteAndroidBuildCredentialsResult = Schema.Struct({ deleted: Schema.Numb
|
|
|
248
249
|
|
|
249
250
|
//#endregion
|
|
250
251
|
//#region ../../packages/api/src/groups/android-build-credentials.ts
|
|
251
|
-
const idParam$
|
|
252
|
+
const idParam$17 = HttpApiSchema.param("id", Schema.String);
|
|
252
253
|
const applicationIdentifierIdParam = HttpApiSchema.param("applicationIdentifierId", Schema.String);
|
|
253
254
|
var AndroidBuildCredentialsGroup = class extends HttpApiGroup.make("androidBuildCredentials").add(HttpApiEndpoint.get("list")`/api/android-application-identifiers/${applicationIdentifierIdParam}/build-credentials`.addSuccess(Schema.Struct({ items: Schema.Array(AndroidBuildCredentials) })).annotateContext(OpenApi.annotations({
|
|
254
255
|
title: "List Android build credentials",
|
|
@@ -256,10 +257,10 @@ var AndroidBuildCredentialsGroup = class extends HttpApiGroup.make("androidBuild
|
|
|
256
257
|
}))).add(HttpApiEndpoint.post("create")`/api/android-application-identifiers/${applicationIdentifierIdParam}/build-credentials`.setPayload(CreateAndroidBuildCredentialsBody).addSuccess(AndroidBuildCredentials, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
257
258
|
title: "Create Android build credentials group",
|
|
258
259
|
description: "Create a named build credentials group (Default or custom)"
|
|
259
|
-
}))).add(HttpApiEndpoint.put("update")`/api/android-build-credentials/${idParam$
|
|
260
|
+
}))).add(HttpApiEndpoint.put("update")`/api/android-build-credentials/${idParam$17}`.setPayload(UpdateAndroidBuildCredentialsBody).addSuccess(AndroidBuildCredentials).annotateContext(OpenApi.annotations({
|
|
260
261
|
title: "Update Android build credentials",
|
|
261
262
|
description: "Rename group, change default flag, or swap bound keystore/keys"
|
|
262
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/android-build-credentials/${idParam$
|
|
263
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/android-build-credentials/${idParam$17}`.addSuccess(DeleteAndroidBuildCredentialsResult).annotateContext(OpenApi.annotations({
|
|
263
264
|
title: "Delete Android build credentials",
|
|
264
265
|
description: "Remove a build credentials group"
|
|
265
266
|
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -299,17 +300,17 @@ const DownloadAndroidUploadKeystoreResult = Schema.Struct({
|
|
|
299
300
|
|
|
300
301
|
//#endregion
|
|
301
302
|
//#region ../../packages/api/src/groups/android-upload-keystores.ts
|
|
302
|
-
const idParam$
|
|
303
|
+
const idParam$16 = HttpApiSchema.param("id", Schema.String);
|
|
303
304
|
var AndroidUploadKeystoresGroup = class extends HttpApiGroup.make("androidUploadKeystores").add(HttpApiEndpoint.get("list", "/api/android/upload-keystores").addSuccess(Schema.Struct({ items: Schema.Array(AndroidUploadKeystore) })).annotateContext(OpenApi.annotations({
|
|
304
305
|
title: "List Android upload keystores",
|
|
305
306
|
description: "List uploaded Android keystores"
|
|
306
307
|
}))).add(HttpApiEndpoint.post("upload", "/api/android/upload-keystores").setPayload(UploadAndroidUploadKeystoreBody).addSuccess(AndroidUploadKeystore, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
307
308
|
title: "Upload Android keystore",
|
|
308
309
|
description: "Upload a JKS/PKCS12 keystore with key alias + passwords"
|
|
309
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/android/upload-keystores/${idParam$
|
|
310
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/android/upload-keystores/${idParam$16}`.addSuccess(DeleteAndroidUploadKeystoreResult).annotateContext(OpenApi.annotations({
|
|
310
311
|
title: "Delete Android keystore",
|
|
311
312
|
description: "Remove a stored Android keystore"
|
|
312
|
-
}))).add(HttpApiEndpoint.get("download")`/api/android/upload-keystores/${idParam$
|
|
313
|
+
}))).add(HttpApiEndpoint.get("download")`/api/android/upload-keystores/${idParam$16}/download`.addSuccess(DownloadAndroidUploadKeystoreResult).annotateContext(OpenApi.annotations({
|
|
313
314
|
title: "Download Android keystore",
|
|
314
315
|
description: "Fetch the decrypted keystore + passwords for local use (audit-logged)"
|
|
315
316
|
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -373,17 +374,17 @@ const DownloadAppleDistributionCertificateResult = Schema.Struct({
|
|
|
373
374
|
|
|
374
375
|
//#endregion
|
|
375
376
|
//#region ../../packages/api/src/groups/apple-distribution-certificates.ts
|
|
376
|
-
const idParam$
|
|
377
|
+
const idParam$15 = HttpApiSchema.param("id", Schema.String);
|
|
377
378
|
var AppleDistributionCertificatesGroup = class extends HttpApiGroup.make("appleDistributionCertificates").add(HttpApiEndpoint.get("list", "/api/apple/distribution-certificates").addSuccess(Schema.Struct({ items: Schema.Array(AppleDistributionCertificate) })).annotateContext(OpenApi.annotations({
|
|
378
379
|
title: "List Apple distribution certificates",
|
|
379
380
|
description: "List uploaded Apple distribution certificates for the organization"
|
|
380
381
|
}))).add(HttpApiEndpoint.post("upload", "/api/apple/distribution-certificates").setPayload(UploadAppleDistributionCertificateBody).addSuccess(AppleDistributionCertificate, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
381
382
|
title: "Upload distribution certificate",
|
|
382
383
|
description: "Upload a .p12 distribution certificate; auto-derives the Apple team from the provided identifier"
|
|
383
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/apple/distribution-certificates/${idParam$
|
|
384
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/apple/distribution-certificates/${idParam$15}`.addSuccess(DeleteAppleDistributionCertificateResult).annotateContext(OpenApi.annotations({
|
|
384
385
|
title: "Delete distribution certificate",
|
|
385
386
|
description: "Remove a distribution certificate from storage"
|
|
386
|
-
}))).add(HttpApiEndpoint.get("download")`/api/apple/distribution-certificates/${idParam$
|
|
387
|
+
}))).add(HttpApiEndpoint.get("download")`/api/apple/distribution-certificates/${idParam$15}/download`.addSuccess(DownloadAppleDistributionCertificateResult).annotateContext(OpenApi.annotations({
|
|
387
388
|
title: "Download distribution certificate",
|
|
388
389
|
description: "Fetch the decrypted .p12 + password for local use (audit-logged)"
|
|
389
390
|
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -431,17 +432,17 @@ const ListAppleProvisioningProfilesParams = Schema.Struct({
|
|
|
431
432
|
|
|
432
433
|
//#endregion
|
|
433
434
|
//#region ../../packages/api/src/groups/apple-provisioning-profiles.ts
|
|
434
|
-
const idParam$
|
|
435
|
+
const idParam$14 = HttpApiSchema.param("id", Schema.String);
|
|
435
436
|
var AppleProvisioningProfilesGroup = class extends HttpApiGroup.make("appleProvisioningProfiles").add(HttpApiEndpoint.get("list", "/api/apple/provisioning-profiles").setUrlParams(ListAppleProvisioningProfilesParams).addSuccess(Schema.Struct({ items: Schema.Array(AppleProvisioningProfile) })).annotateContext(OpenApi.annotations({
|
|
436
437
|
title: "List provisioning profiles",
|
|
437
438
|
description: "List stored provisioning profiles, optionally filtered by bundle + team"
|
|
438
439
|
}))).add(HttpApiEndpoint.post("upload", "/api/apple/provisioning-profiles").setPayload(UploadAppleProvisioningProfileBody).addSuccess(AppleProvisioningProfile, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
439
440
|
title: "Upload provisioning profile",
|
|
440
441
|
description: "Upload an existing or freshly generated .mobileprovision; auto-parses the embedded plist"
|
|
441
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/apple/provisioning-profiles/${idParam$
|
|
442
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/apple/provisioning-profiles/${idParam$14}`.addSuccess(DeleteAppleProvisioningProfileResult).annotateContext(OpenApi.annotations({
|
|
442
443
|
title: "Delete provisioning profile",
|
|
443
444
|
description: "Remove a stored provisioning profile"
|
|
444
|
-
}))).add(HttpApiEndpoint.get("download")`/api/apple/provisioning-profiles/${idParam$
|
|
445
|
+
}))).add(HttpApiEndpoint.get("download")`/api/apple/provisioning-profiles/${idParam$14}/download`.addSuccess(DownloadAppleProvisioningProfileResult).annotateContext(OpenApi.annotations({
|
|
445
446
|
title: "Download provisioning profile",
|
|
446
447
|
description: "Fetch the decoded .mobileprovision for local use (audit-logged)"
|
|
447
448
|
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -477,17 +478,17 @@ const DownloadApplePushKeyResult = Schema.Struct({
|
|
|
477
478
|
|
|
478
479
|
//#endregion
|
|
479
480
|
//#region ../../packages/api/src/groups/apple-push-keys.ts
|
|
480
|
-
const idParam$
|
|
481
|
+
const idParam$13 = HttpApiSchema.param("id", Schema.String);
|
|
481
482
|
var ApplePushKeysGroup = class extends HttpApiGroup.make("applePushKeys").add(HttpApiEndpoint.get("list", "/api/apple/push-keys").addSuccess(Schema.Struct({ items: Schema.Array(ApplePushKey) })).annotateContext(OpenApi.annotations({
|
|
482
483
|
title: "List Apple push keys",
|
|
483
484
|
description: "List APNs push keys for the organization"
|
|
484
485
|
}))).add(HttpApiEndpoint.post("upload", "/api/apple/push-keys").setPayload(UploadApplePushKeyBody).addSuccess(ApplePushKey, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
485
486
|
title: "Upload push key",
|
|
486
487
|
description: "Upload an APNs .p8 push notification key"
|
|
487
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/apple/push-keys/${idParam$
|
|
488
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/apple/push-keys/${idParam$13}`.addSuccess(DeleteApplePushKeyResult).annotateContext(OpenApi.annotations({
|
|
488
489
|
title: "Delete push key",
|
|
489
490
|
description: "Remove a stored APNs push key"
|
|
490
|
-
}))).add(HttpApiEndpoint.get("download")`/api/apple/push-keys/${idParam$
|
|
491
|
+
}))).add(HttpApiEndpoint.get("download")`/api/apple/push-keys/${idParam$13}/download`.addSuccess(DownloadApplePushKeyResult).annotateContext(OpenApi.annotations({
|
|
491
492
|
title: "Download push key",
|
|
492
493
|
description: "Fetch the decrypted .p8 push key for local use (audit-logged)"
|
|
493
494
|
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -514,6 +515,7 @@ var AscApiKey = class extends Schema.Class("AscApiKey")({
|
|
|
514
515
|
organizationId: Id,
|
|
515
516
|
appleTeamId: Schema.NullOr(Id),
|
|
516
517
|
keyId: Schema.String,
|
|
518
|
+
issuerId: IssuerId,
|
|
517
519
|
name: Schema.String,
|
|
518
520
|
roles: Schema.Array(Schema.String),
|
|
519
521
|
createdAt: DateTimeString,
|
|
@@ -560,20 +562,20 @@ const SyncDevicesResult = Schema.Struct({
|
|
|
560
562
|
|
|
561
563
|
//#endregion
|
|
562
564
|
//#region ../../packages/api/src/groups/asc-api-keys.ts
|
|
563
|
-
const idParam$
|
|
565
|
+
const idParam$12 = HttpApiSchema.param("id", Schema.String);
|
|
564
566
|
var AscApiKeysGroup = class extends HttpApiGroup.make("ascApiKeys").add(HttpApiEndpoint.get("list", "/api/apple/asc-api-keys").addSuccess(Schema.Struct({ items: Schema.Array(AscApiKey) })).annotateContext(OpenApi.annotations({
|
|
565
567
|
title: "List App Store Connect API keys",
|
|
566
568
|
description: "List stored ASC API keys for the organization"
|
|
567
569
|
}))).add(HttpApiEndpoint.post("upload", "/api/apple/asc-api-keys").setPayload(UploadAscApiKeyBody).addSuccess(AscApiKey, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
568
570
|
title: "Upload ASC API key",
|
|
569
571
|
description: "Upload an App Store Connect API key (.p8 + issuer)"
|
|
570
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/apple/asc-api-keys/${idParam$
|
|
572
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/apple/asc-api-keys/${idParam$12}`.addSuccess(DeleteAscApiKeyResult).annotateContext(OpenApi.annotations({
|
|
571
573
|
title: "Delete ASC API key",
|
|
572
574
|
description: "Remove a stored ASC API key"
|
|
573
|
-
}))).add(HttpApiEndpoint.post("syncDevices")`/api/apple/asc-api-keys/${idParam$
|
|
575
|
+
}))).add(HttpApiEndpoint.post("syncDevices")`/api/apple/asc-api-keys/${idParam$12}/sync-devices`.addSuccess(SyncDevicesResult).annotateContext(OpenApi.annotations({
|
|
574
576
|
title: "Sync devices via ASC API key",
|
|
575
577
|
description: "Pull registered devices from Apple Developer Portal for this key's team; push local devices that aren't yet registered"
|
|
576
|
-
}))).add(HttpApiEndpoint.get("getCredentials")`/api/apple/asc-api-keys/${idParam$
|
|
578
|
+
}))).add(HttpApiEndpoint.get("getCredentials")`/api/apple/asc-api-keys/${idParam$12}/credentials`.addSuccess(AscApiKeyCredentials).annotateContext(OpenApi.annotations({
|
|
577
579
|
title: "Get ASC API key credentials",
|
|
578
580
|
description: "Return the decrypted .p8 PEM, keyId, issuerId, and Apple team for direct App Store Connect API calls from the CLI"
|
|
579
581
|
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -627,7 +629,7 @@ var AssetsGroup = class extends HttpApiGroup.make("assets").add(HttpApiEndpoint.
|
|
|
627
629
|
|
|
628
630
|
//#endregion
|
|
629
631
|
//#region ../../packages/api/src/domain/audit-log.ts
|
|
630
|
-
const AuditLogResourceType = Schema.Literal("project", "branch", "channel", "update", "build", "appleCredential", "androidCredential", "iosBundleConfiguration", "envVar", "device", "webhook");
|
|
632
|
+
const AuditLogResourceType = Schema.Literal("project", "branch", "channel", "update", "build", "appleCredential", "androidCredential", "iosBundleConfiguration", "envVar", "device", "webhook", "iosAppMetadata", "submission");
|
|
631
633
|
const AuditLogSource = Schema.Literal("session", "api-key");
|
|
632
634
|
var AuditLog = class extends Schema.Class("AuditLog")({
|
|
633
635
|
id: Id,
|
|
@@ -687,7 +689,7 @@ const DeleteBranchResult = Schema.Struct({ deleted: Schema.Number });
|
|
|
687
689
|
|
|
688
690
|
//#endregion
|
|
689
691
|
//#region ../../packages/api/src/groups/branches.ts
|
|
690
|
-
const idParam$
|
|
692
|
+
const idParam$11 = HttpApiSchema.param("id", Schema.String);
|
|
691
693
|
var BranchesGroup = class extends HttpApiGroup.make("branches").add(HttpApiEndpoint.post("create", "/api/branches").setPayload(CreateBranchBody).addSuccess(Branch, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
692
694
|
title: "Create branch",
|
|
693
695
|
description: "Create a new branch within a project"
|
|
@@ -699,13 +701,13 @@ var BranchesGroup = class extends HttpApiGroup.make("branches").add(HttpApiEndpo
|
|
|
699
701
|
})).annotateContext(OpenApi.annotations({
|
|
700
702
|
title: "List branches",
|
|
701
703
|
description: "List all branches for a project"
|
|
702
|
-
}))).add(HttpApiEndpoint.get("get")`/api/branches/${idParam$
|
|
704
|
+
}))).add(HttpApiEndpoint.get("get")`/api/branches/${idParam$11}`.addSuccess(Branch).annotateContext(OpenApi.annotations({
|
|
703
705
|
title: "Get branch",
|
|
704
706
|
description: "Fetch a single branch by ID"
|
|
705
|
-
}))).add(HttpApiEndpoint.patch("rename")`/api/branches/${idParam$
|
|
707
|
+
}))).add(HttpApiEndpoint.patch("rename")`/api/branches/${idParam$11}`.setPayload(UpdateBranchBody).addSuccess(Branch).addError(Conflict).annotateContext(OpenApi.annotations({
|
|
706
708
|
title: "Rename branch",
|
|
707
709
|
description: "Rename a branch (channels and updates are unaffected)"
|
|
708
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/branches/${idParam$
|
|
710
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/branches/${idParam$11}`.addSuccess(DeleteBranchResult).annotateContext(OpenApi.annotations({
|
|
709
711
|
title: "Delete branch",
|
|
710
712
|
description: "Delete a branch and all its updates"
|
|
711
713
|
}))).addError(NotFound).addError(Conflict).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -779,8 +781,8 @@ const ResolveBuildCredentialsResult = Schema.Union(ResolveBuildCredentialsIosRes
|
|
|
779
781
|
|
|
780
782
|
//#endregion
|
|
781
783
|
//#region ../../packages/api/src/groups/build-credentials.ts
|
|
782
|
-
const projectIdParam$
|
|
783
|
-
var BuildCredentialsGroup = class extends HttpApiGroup.make("buildCredentials").add(HttpApiEndpoint.post("resolve")`/api/projects/${projectIdParam$
|
|
784
|
+
const projectIdParam$5 = HttpApiSchema.param("projectId", Schema.String);
|
|
785
|
+
var BuildCredentialsGroup = class extends HttpApiGroup.make("buildCredentials").add(HttpApiEndpoint.post("resolve")`/api/projects/${projectIdParam$5}/build-credentials/resolve`.setPayload(ResolveBuildCredentialsBody).addSuccess(ResolveBuildCredentialsResult).annotateContext(OpenApi.annotations({
|
|
784
786
|
title: "Resolve build credentials",
|
|
785
787
|
description: "Return decrypted signing assets for a project build. Regenerates the iOS provisioning profile via Apple ASC when the registered device roster has changed since the profile was last generated."
|
|
786
788
|
}))).addError(NotFound).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -940,11 +942,11 @@ const BuildCompatibilityMatrixResult = Schema.Struct({
|
|
|
940
942
|
|
|
941
943
|
//#endregion
|
|
942
944
|
//#region ../../packages/api/src/groups/builds.ts
|
|
943
|
-
const idParam$
|
|
945
|
+
const idParam$10 = HttpApiSchema.param("id", Schema.String);
|
|
944
946
|
var BuildsGroup = class extends HttpApiGroup.make("builds").add(HttpApiEndpoint.post("reserve", "/api/builds").setPayload(CreateBuildBody).addSuccess(ReserveBuildResult, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
945
947
|
title: "Reserve build",
|
|
946
948
|
description: "Reserve a build ID and get a presigned upload URL"
|
|
947
|
-
}))).add(HttpApiEndpoint.post("complete")`/api/builds/${idParam$
|
|
949
|
+
}))).add(HttpApiEndpoint.post("complete")`/api/builds/${idParam$10}/complete`.setPayload(CompleteBuildBody).addSuccess(BuildWithArtifact).addError(Conflict).annotateContext(OpenApi.annotations({
|
|
948
950
|
title: "Complete build",
|
|
949
951
|
description: "Finalize a build after artifact upload"
|
|
950
952
|
}))).add(HttpApiEndpoint.get("list", "/api/builds").setUrlParams(ListBuildsParams).addSuccess(Schema.Struct({
|
|
@@ -958,13 +960,13 @@ var BuildsGroup = class extends HttpApiGroup.make("builds").add(HttpApiEndpoint.
|
|
|
958
960
|
}))).add(HttpApiEndpoint.get("compatibilityMatrix", "/api/builds/compatibility-matrix").setUrlParams(Schema.Struct({ projectId: Id })).addSuccess(BuildCompatibilityMatrixResult).annotateContext(OpenApi.annotations({
|
|
959
961
|
title: "Build compatibility matrix",
|
|
960
962
|
description: "List build-to-channel OTA compatibility for a project"
|
|
961
|
-
}))).add(HttpApiEndpoint.get("get")`/api/builds/${idParam$
|
|
963
|
+
}))).add(HttpApiEndpoint.get("get")`/api/builds/${idParam$10}`.addSuccess(BuildWithArtifact).annotateContext(OpenApi.annotations({
|
|
962
964
|
title: "Get build",
|
|
963
965
|
description: "Get a build by ID with artifact details"
|
|
964
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/builds/${idParam$
|
|
966
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/builds/${idParam$10}`.addSuccess(DeleteBuildResult).annotateContext(OpenApi.annotations({
|
|
965
967
|
title: "Delete build",
|
|
966
968
|
description: "Delete a build and its artifact from storage"
|
|
967
|
-
}))).add(HttpApiEndpoint.get("getInstallLink")`/api/builds/${idParam$
|
|
969
|
+
}))).add(HttpApiEndpoint.get("getInstallLink")`/api/builds/${idParam$10}/install-link`.addSuccess(InstallLinkResult).annotateContext(OpenApi.annotations({
|
|
968
970
|
title: "Get install link",
|
|
969
971
|
description: "Generate a signed install link for a build artifact"
|
|
970
972
|
}))).addError(NotFound).addError(Forbidden).addError(BadRequest).annotateContext(OpenApi.annotations({
|
|
@@ -1009,11 +1011,11 @@ const DeleteChannelResult = Schema.Struct({ deleted: Schema.Number });
|
|
|
1009
1011
|
|
|
1010
1012
|
//#endregion
|
|
1011
1013
|
//#region ../../packages/api/src/groups/channels.ts
|
|
1012
|
-
const idParam$
|
|
1014
|
+
const idParam$9 = HttpApiSchema.param("id", Schema.String);
|
|
1013
1015
|
var ChannelsGroup = class extends HttpApiGroup.make("channels").add(HttpApiEndpoint.post("create", "/api/channels").setPayload(CreateChannelBody).addSuccess(Channel, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
1014
1016
|
title: "Create channel",
|
|
1015
1017
|
description: "Create a new channel linked to a branch"
|
|
1016
|
-
}))).add(HttpApiEndpoint.patch("update")`/api/channels/${idParam$
|
|
1018
|
+
}))).add(HttpApiEndpoint.patch("update")`/api/channels/${idParam$9}`.setPayload(UpdateChannelBody).addSuccess(Channel).annotateContext(OpenApi.annotations({
|
|
1017
1019
|
title: "Update channel",
|
|
1018
1020
|
description: "Relink channel to a different branch"
|
|
1019
1021
|
}))).add(HttpApiEndpoint.get("list", "/api/channels").setUrlParams(ListChannelsParams).addSuccess(Schema.Struct({
|
|
@@ -1024,25 +1026,25 @@ var ChannelsGroup = class extends HttpApiGroup.make("channels").add(HttpApiEndpo
|
|
|
1024
1026
|
})).annotateContext(OpenApi.annotations({
|
|
1025
1027
|
title: "List channels",
|
|
1026
1028
|
description: "List all channels for a project"
|
|
1027
|
-
}))).add(HttpApiEndpoint.post("pause")`/api/channels/${idParam$
|
|
1029
|
+
}))).add(HttpApiEndpoint.post("pause")`/api/channels/${idParam$9}/pause`.addSuccess(Channel).annotateContext(OpenApi.annotations({
|
|
1028
1030
|
title: "Pause channel",
|
|
1029
1031
|
description: "Pause a channel — manifest requests return 204 No Content"
|
|
1030
|
-
}))).add(HttpApiEndpoint.post("resume")`/api/channels/${idParam$
|
|
1032
|
+
}))).add(HttpApiEndpoint.post("resume")`/api/channels/${idParam$9}/resume`.addSuccess(Channel).annotateContext(OpenApi.annotations({
|
|
1031
1033
|
title: "Resume channel",
|
|
1032
1034
|
description: "Resume a paused channel"
|
|
1033
|
-
}))).add(HttpApiEndpoint.post("createBranchRollout")`/api/channels/${idParam$
|
|
1035
|
+
}))).add(HttpApiEndpoint.post("createBranchRollout")`/api/channels/${idParam$9}/rollout`.setPayload(CreateBranchRolloutBody).addSuccess(Channel).annotateContext(OpenApi.annotations({
|
|
1034
1036
|
title: "Create branch rollout",
|
|
1035
1037
|
description: "Start a gradual rollout to a new branch on this channel"
|
|
1036
|
-
}))).add(HttpApiEndpoint.patch("updateBranchRollout")`/api/channels/${idParam$
|
|
1038
|
+
}))).add(HttpApiEndpoint.patch("updateBranchRollout")`/api/channels/${idParam$9}/rollout`.setPayload(UpdateRolloutBody).addSuccess(Channel).annotateContext(OpenApi.annotations({
|
|
1037
1039
|
title: "Update branch rollout",
|
|
1038
1040
|
description: "Change the rollout percentage for a branch rollout"
|
|
1039
|
-
}))).add(HttpApiEndpoint.post("completeBranchRollout")`/api/channels/${idParam$
|
|
1041
|
+
}))).add(HttpApiEndpoint.post("completeBranchRollout")`/api/channels/${idParam$9}/rollout/complete`.addSuccess(Channel).annotateContext(OpenApi.annotations({
|
|
1040
1042
|
title: "Complete branch rollout",
|
|
1041
1043
|
description: "Finalize the rollout — promote the new branch to 100%"
|
|
1042
|
-
}))).add(HttpApiEndpoint.post("revertBranchRollout")`/api/channels/${idParam$
|
|
1044
|
+
}))).add(HttpApiEndpoint.post("revertBranchRollout")`/api/channels/${idParam$9}/rollout/revert`.addSuccess(Channel).annotateContext(OpenApi.annotations({
|
|
1043
1045
|
title: "Revert branch rollout",
|
|
1044
1046
|
description: "Revert the rollout — restore the original branch"
|
|
1045
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/channels/${idParam$
|
|
1047
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/channels/${idParam$9}`.addSuccess(DeleteChannelResult).annotateContext(OpenApi.annotations({
|
|
1046
1048
|
title: "Delete channel",
|
|
1047
1049
|
description: "Delete a channel"
|
|
1048
1050
|
}))).addError(NotFound).addError(Conflict).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -1119,7 +1121,7 @@ const ListRegistrationRequestsParams = Schema.Struct({
|
|
|
1119
1121
|
|
|
1120
1122
|
//#endregion
|
|
1121
1123
|
//#region ../../packages/api/src/groups/devices.ts
|
|
1122
|
-
const idParam$
|
|
1124
|
+
const idParam$8 = HttpApiSchema.param("id", Schema.String);
|
|
1123
1125
|
var DevicesGroup = class extends HttpApiGroup.make("devices").add(HttpApiEndpoint.post("register", "/api/devices").setPayload(RegisterDeviceBody).addSuccess(Device, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
1124
1126
|
title: "Register device",
|
|
1125
1127
|
description: "Register an Apple device UDID in the caller's active organization"
|
|
@@ -1131,13 +1133,13 @@ var DevicesGroup = class extends HttpApiGroup.make("devices").add(HttpApiEndpoin
|
|
|
1131
1133
|
})).annotateContext(OpenApi.annotations({
|
|
1132
1134
|
title: "List devices",
|
|
1133
1135
|
description: "List registered Apple devices in the caller's active organization"
|
|
1134
|
-
}))).add(HttpApiEndpoint.get("get")`/api/devices/${idParam$
|
|
1136
|
+
}))).add(HttpApiEndpoint.get("get")`/api/devices/${idParam$8}`.addSuccess(Device).annotateContext(OpenApi.annotations({
|
|
1135
1137
|
title: "Get device",
|
|
1136
1138
|
description: "Get a single device by ID"
|
|
1137
|
-
}))).add(HttpApiEndpoint.patch("update")`/api/devices/${idParam$
|
|
1139
|
+
}))).add(HttpApiEndpoint.patch("update")`/api/devices/${idParam$8}`.setPayload(UpdateDeviceBody).addSuccess(Device).annotateContext(OpenApi.annotations({
|
|
1138
1140
|
title: "Update device",
|
|
1139
1141
|
description: "Rename a device or toggle its enabled state"
|
|
1140
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/devices/${idParam$
|
|
1142
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/devices/${idParam$8}`.addSuccess(DeleteDeviceResult).annotateContext(OpenApi.annotations({
|
|
1141
1143
|
title: "Delete device",
|
|
1142
1144
|
description: "Remove a registered device from the organization"
|
|
1143
1145
|
}))).add(HttpApiEndpoint.post("createRegistrationRequest", "/api/devices/registration-requests").setPayload(CreateRegistrationRequestBody).addSuccess(DeviceRegistrationRequest, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
@@ -1217,7 +1219,7 @@ const EnvVarExportResult = Schema.Struct({
|
|
|
1217
1219
|
|
|
1218
1220
|
//#endregion
|
|
1219
1221
|
//#region ../../packages/api/src/groups/env-vars.ts
|
|
1220
|
-
const idParam$
|
|
1222
|
+
const idParam$7 = HttpApiSchema.param("id", Schema.String);
|
|
1221
1223
|
var EnvVarsGroup = class extends HttpApiGroup.make("env-vars").add(HttpApiEndpoint.post("create", "/api/env-vars").setPayload(CreateEnvVarBody).addSuccess(EnvVar, { status: 201 }).addError(BadRequest).addError(Conflict).annotateContext(OpenApi.annotations({
|
|
1222
1224
|
title: "Create environment variable",
|
|
1223
1225
|
description: "Create a new environment variable. Scope can be 'project' (requires projectId) or 'global' (organization-wide)."
|
|
@@ -1230,13 +1232,13 @@ var EnvVarsGroup = class extends HttpApiGroup.make("env-vars").add(HttpApiEndpoi
|
|
|
1230
1232
|
})).addSuccess(Schema.Struct({ items: Schema.Array(EnvVar) })).addError(BadRequest).annotateContext(OpenApi.annotations({
|
|
1231
1233
|
title: "List environment variables",
|
|
1232
1234
|
description: "List environment variables. scope=all merges project + global vars with project overrides. environments is a comma-separated list. search matches key substring."
|
|
1233
|
-
}))).add(HttpApiEndpoint.get("get")`/api/env-vars/${idParam$
|
|
1235
|
+
}))).add(HttpApiEndpoint.get("get")`/api/env-vars/${idParam$7}`.addSuccess(EnvVar).annotateContext(OpenApi.annotations({
|
|
1234
1236
|
title: "Get environment variable",
|
|
1235
1237
|
description: "Get an environment variable by ID"
|
|
1236
|
-
}))).add(HttpApiEndpoint.patch("update")`/api/env-vars/${idParam$
|
|
1238
|
+
}))).add(HttpApiEndpoint.patch("update")`/api/env-vars/${idParam$7}`.setPayload(UpdateEnvVarBody).addSuccess(EnvVar).addError(BadRequest).annotateContext(OpenApi.annotations({
|
|
1237
1239
|
title: "Update environment variable",
|
|
1238
1240
|
description: "Update value, visibility, or assigned environments"
|
|
1239
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/env-vars/${idParam$
|
|
1241
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/env-vars/${idParam$7}`.addSuccess(DeleteEnvVarResult).annotateContext(OpenApi.annotations({
|
|
1240
1242
|
title: "Delete environment variable",
|
|
1241
1243
|
description: "Delete an environment variable"
|
|
1242
1244
|
}))).add(HttpApiEndpoint.post("bulkImport", "/api/env-vars/bulk-import").setPayload(BulkImportEnvVarsBody).addSuccess(BulkImportResult).addError(BadRequest).annotateContext(OpenApi.annotations({
|
|
@@ -1342,7 +1344,7 @@ const DeleteUpdateResult = Schema.Struct({ deleted: Schema.Number });
|
|
|
1342
1344
|
|
|
1343
1345
|
//#endregion
|
|
1344
1346
|
//#region ../../packages/api/src/groups/fingerprints.ts
|
|
1345
|
-
const projectIdParam$
|
|
1347
|
+
const projectIdParam$4 = HttpApiSchema.param("projectId", Id);
|
|
1346
1348
|
const hashParam = HttpApiSchema.param("hash", Schema.String.pipe(Schema.minLength(1)));
|
|
1347
1349
|
const FingerprintDetail = Schema.Struct({
|
|
1348
1350
|
hash: Schema.String,
|
|
@@ -1350,7 +1352,7 @@ const FingerprintDetail = Schema.Struct({
|
|
|
1350
1352
|
builds: Schema.Array(BuildWithArtifact),
|
|
1351
1353
|
updates: Schema.Array(Update)
|
|
1352
1354
|
});
|
|
1353
|
-
var FingerprintsGroup = class extends HttpApiGroup.make("fingerprints").add(HttpApiEndpoint.get("get")`/api/projects/${projectIdParam$
|
|
1355
|
+
var FingerprintsGroup = class extends HttpApiGroup.make("fingerprints").add(HttpApiEndpoint.get("get")`/api/projects/${projectIdParam$4}/fingerprints/${hashParam}`.addSuccess(FingerprintDetail).annotateContext(OpenApi.annotations({
|
|
1354
1356
|
title: "Get fingerprint",
|
|
1355
1357
|
description: "Fetch builds and updates compatible with a given fingerprint hash within a project."
|
|
1356
1358
|
}))).addError(Forbidden, { status: 403 }).addError(NotFound, { status: 404 }).addError(BadRequest, { status: 400 }) {};
|
|
@@ -1376,17 +1378,17 @@ const DownloadGoogleServiceAccountKeyResult = Schema.Struct({
|
|
|
1376
1378
|
|
|
1377
1379
|
//#endregion
|
|
1378
1380
|
//#region ../../packages/api/src/groups/google-service-account-keys.ts
|
|
1379
|
-
const idParam$
|
|
1381
|
+
const idParam$6 = HttpApiSchema.param("id", Schema.String);
|
|
1380
1382
|
var GoogleServiceAccountKeysGroup = class extends HttpApiGroup.make("googleServiceAccountKeys").add(HttpApiEndpoint.get("list", "/api/google/service-account-keys").addSuccess(Schema.Struct({ items: Schema.Array(GoogleServiceAccountKey) })).annotateContext(OpenApi.annotations({
|
|
1381
1383
|
title: "List Google service account keys",
|
|
1382
1384
|
description: "List uploaded Google service account JSON keys"
|
|
1383
1385
|
}))).add(HttpApiEndpoint.post("upload", "/api/google/service-account-keys").setPayload(UploadGoogleServiceAccountKeyBody).addSuccess(GoogleServiceAccountKey, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
1384
1386
|
title: "Upload service account key",
|
|
1385
1387
|
description: "Upload a Google service account JSON key"
|
|
1386
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/google/service-account-keys/${idParam$
|
|
1388
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/google/service-account-keys/${idParam$6}`.addSuccess(DeleteGoogleServiceAccountKeyResult).annotateContext(OpenApi.annotations({
|
|
1387
1389
|
title: "Delete service account key",
|
|
1388
1390
|
description: "Remove a stored Google service account key"
|
|
1389
|
-
}))).add(HttpApiEndpoint.get("download")`/api/google/service-account-keys/${idParam$
|
|
1391
|
+
}))).add(HttpApiEndpoint.get("download")`/api/google/service-account-keys/${idParam$6}/download`.addSuccess(DownloadGoogleServiceAccountKeyResult).annotateContext(OpenApi.annotations({
|
|
1390
1392
|
title: "Download service account key",
|
|
1391
1393
|
description: "Fetch the decrypted JSON for local use (audit-logged)"
|
|
1392
1394
|
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -1394,8 +1396,67 @@ var GoogleServiceAccountKeysGroup = class extends HttpApiGroup.make("googleServi
|
|
|
1394
1396
|
description: "Manage Google Play + FCM service account JSON keys"
|
|
1395
1397
|
})) {};
|
|
1396
1398
|
|
|
1399
|
+
//#endregion
|
|
1400
|
+
//#region ../../packages/api/src/domain/ios-app-metadata.ts
|
|
1401
|
+
const AscAppId = Schema.String.pipe(Schema.pattern(/^[0-9]{1,30}$/u, { message: () => "ASC App ID must be 1-30 digits" }));
|
|
1402
|
+
const AppStoreLanguage = Schema.String.pipe(Schema.minLength(2), Schema.maxLength(10));
|
|
1403
|
+
const AppStoreSku = Schema.String.pipe(Schema.minLength(1), Schema.maxLength(100));
|
|
1404
|
+
const CompanyName = Schema.String.pipe(Schema.minLength(1), Schema.maxLength(200));
|
|
1405
|
+
const AppName = Schema.String.pipe(Schema.minLength(1), Schema.maxLength(200));
|
|
1406
|
+
var IosAppMetadata = class extends Schema.Class("IosAppMetadata")({
|
|
1407
|
+
id: Id,
|
|
1408
|
+
organizationId: Id,
|
|
1409
|
+
projectId: Id,
|
|
1410
|
+
bundleIdentifier: Schema.String,
|
|
1411
|
+
ascAppId: Schema.NullOr(Schema.String),
|
|
1412
|
+
sku: Schema.NullOr(Schema.String),
|
|
1413
|
+
language: Schema.String,
|
|
1414
|
+
companyName: Schema.NullOr(Schema.String),
|
|
1415
|
+
appName: Schema.NullOr(Schema.String),
|
|
1416
|
+
createdAt: DateTimeString,
|
|
1417
|
+
updatedAt: DateTimeString
|
|
1418
|
+
}) {};
|
|
1419
|
+
const CreateIosAppMetadataBody = Schema.Struct({
|
|
1420
|
+
bundleIdentifier: BundleIdentifier,
|
|
1421
|
+
ascAppId: Schema.optional(AscAppId),
|
|
1422
|
+
sku: Schema.optional(AppStoreSku),
|
|
1423
|
+
language: Schema.optional(AppStoreLanguage),
|
|
1424
|
+
companyName: Schema.optional(CompanyName),
|
|
1425
|
+
appName: Schema.optional(AppName)
|
|
1426
|
+
});
|
|
1427
|
+
const UpdateIosAppMetadataBody = Schema.Struct({
|
|
1428
|
+
ascAppId: Schema.optional(Schema.NullOr(AscAppId)),
|
|
1429
|
+
sku: Schema.optional(Schema.NullOr(AppStoreSku)),
|
|
1430
|
+
language: Schema.optional(AppStoreLanguage),
|
|
1431
|
+
companyName: Schema.optional(Schema.NullOr(CompanyName)),
|
|
1432
|
+
appName: Schema.optional(Schema.NullOr(AppName))
|
|
1433
|
+
});
|
|
1434
|
+
const DeleteIosAppMetadataResult = Schema.Struct({ deleted: Schema.Number });
|
|
1435
|
+
|
|
1436
|
+
//#endregion
|
|
1437
|
+
//#region ../../packages/api/src/groups/ios-app-metadata.ts
|
|
1438
|
+
const idParam$5 = HttpApiSchema.param("id", Schema.String);
|
|
1439
|
+
const projectIdParam$3 = HttpApiSchema.param("projectId", Schema.String);
|
|
1440
|
+
var IosAppMetadataGroup = class extends HttpApiGroup.make("iosAppMetadata").add(HttpApiEndpoint.get("list")`/api/projects/${projectIdParam$3}/ios-app-metadata`.addSuccess(Schema.Struct({ items: Schema.Array(IosAppMetadata) })).annotateContext(OpenApi.annotations({
|
|
1441
|
+
title: "List iOS App Store metadata",
|
|
1442
|
+
description: "List App Store Connect metadata entries for a project"
|
|
1443
|
+
}))).add(HttpApiEndpoint.post("create")`/api/projects/${projectIdParam$3}/ios-app-metadata`.setPayload(CreateIosAppMetadataBody).addSuccess(IosAppMetadata, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
1444
|
+
title: "Create iOS App Store metadata",
|
|
1445
|
+
description: "Register App Store Connect metadata for a bundle identifier"
|
|
1446
|
+
}))).add(HttpApiEndpoint.put("update")`/api/ios-app-metadata/${idParam$5}`.setPayload(UpdateIosAppMetadataBody).addSuccess(IosAppMetadata).annotateContext(OpenApi.annotations({
|
|
1447
|
+
title: "Update iOS App Store metadata",
|
|
1448
|
+
description: "Change ASC app id / sku / language / company name / app name"
|
|
1449
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/ios-app-metadata/${idParam$5}`.addSuccess(DeleteIosAppMetadataResult).annotateContext(OpenApi.annotations({
|
|
1450
|
+
title: "Delete iOS App Store metadata",
|
|
1451
|
+
description: "Remove an App Store metadata entry"
|
|
1452
|
+
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
1453
|
+
title: "iOS App Store metadata",
|
|
1454
|
+
description: "Per-project per-bundle App Store Connect metadata for submissions"
|
|
1455
|
+
})) {};
|
|
1456
|
+
|
|
1397
1457
|
//#endregion
|
|
1398
1458
|
//#region ../../packages/api/src/domain/ios-bundle-configuration.ts
|
|
1459
|
+
const TargetName = Schema.String.pipe(Schema.minLength(1), Schema.maxLength(200));
|
|
1399
1460
|
var IosBundleConfiguration = class extends Schema.Class("IosBundleConfiguration")({
|
|
1400
1461
|
id: Id,
|
|
1401
1462
|
organizationId: Id,
|
|
@@ -1407,6 +1468,8 @@ var IosBundleConfiguration = class extends Schema.Class("IosBundleConfiguration"
|
|
|
1407
1468
|
appleProvisioningProfileId: Schema.NullOr(Id),
|
|
1408
1469
|
applePushKeyId: Schema.NullOr(Id),
|
|
1409
1470
|
ascApiKeyId: Schema.NullOr(Id),
|
|
1471
|
+
targetName: Schema.NullOr(Schema.String),
|
|
1472
|
+
parentBundleIdentifier: Schema.NullOr(Schema.String),
|
|
1410
1473
|
createdAt: DateTimeString,
|
|
1411
1474
|
updatedAt: DateTimeString
|
|
1412
1475
|
}) {};
|
|
@@ -1417,30 +1480,34 @@ const CreateIosBundleConfigurationBody = Schema.Struct({
|
|
|
1417
1480
|
appleDistributionCertificateId: Schema.optional(Id),
|
|
1418
1481
|
appleProvisioningProfileId: Schema.optional(Id),
|
|
1419
1482
|
applePushKeyId: Schema.optional(Id),
|
|
1420
|
-
ascApiKeyId: Schema.optional(Id)
|
|
1483
|
+
ascApiKeyId: Schema.optional(Id),
|
|
1484
|
+
targetName: Schema.optional(TargetName),
|
|
1485
|
+
parentBundleIdentifier: Schema.optional(BundleIdentifier)
|
|
1421
1486
|
});
|
|
1422
1487
|
const UpdateIosBundleConfigurationBody = Schema.Struct({
|
|
1423
1488
|
appleDistributionCertificateId: Schema.optional(Schema.NullOr(Id)),
|
|
1424
1489
|
appleProvisioningProfileId: Schema.optional(Schema.NullOr(Id)),
|
|
1425
1490
|
applePushKeyId: Schema.optional(Schema.NullOr(Id)),
|
|
1426
|
-
ascApiKeyId: Schema.optional(Schema.NullOr(Id))
|
|
1491
|
+
ascApiKeyId: Schema.optional(Schema.NullOr(Id)),
|
|
1492
|
+
targetName: Schema.optional(Schema.NullOr(TargetName)),
|
|
1493
|
+
parentBundleIdentifier: Schema.optional(Schema.NullOr(BundleIdentifier))
|
|
1427
1494
|
});
|
|
1428
1495
|
const DeleteIosBundleConfigurationResult = Schema.Struct({ deleted: Schema.Number });
|
|
1429
1496
|
|
|
1430
1497
|
//#endregion
|
|
1431
1498
|
//#region ../../packages/api/src/groups/ios-bundle-configurations.ts
|
|
1432
|
-
const idParam$
|
|
1433
|
-
const projectIdParam$
|
|
1434
|
-
var IosBundleConfigurationsGroup = class extends HttpApiGroup.make("iosBundleConfigurations").add(HttpApiEndpoint.get("list")`/api/projects/${projectIdParam$
|
|
1499
|
+
const idParam$4 = HttpApiSchema.param("id", Schema.String);
|
|
1500
|
+
const projectIdParam$2 = HttpApiSchema.param("projectId", Schema.String);
|
|
1501
|
+
var IosBundleConfigurationsGroup = class extends HttpApiGroup.make("iosBundleConfigurations").add(HttpApiEndpoint.get("list")`/api/projects/${projectIdParam$2}/ios-bundle-configurations`.addSuccess(Schema.Struct({ items: Schema.Array(IosBundleConfiguration) })).annotateContext(OpenApi.annotations({
|
|
1435
1502
|
title: "List iOS bundle configurations",
|
|
1436
1503
|
description: "List all iOS bundle configurations for a project"
|
|
1437
|
-
}))).add(HttpApiEndpoint.post("create")`/api/projects/${projectIdParam$
|
|
1504
|
+
}))).add(HttpApiEndpoint.post("create")`/api/projects/${projectIdParam$2}/ios-bundle-configurations`.setPayload(CreateIosBundleConfigurationBody).addSuccess(IosBundleConfiguration, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
1438
1505
|
title: "Create iOS bundle configuration",
|
|
1439
1506
|
description: "Bind certificate + profile + push + ASC to a bundle identifier"
|
|
1440
|
-
}))).add(HttpApiEndpoint.put("update")`/api/ios-bundle-configurations/${idParam$
|
|
1507
|
+
}))).add(HttpApiEndpoint.put("update")`/api/ios-bundle-configurations/${idParam$4}`.setPayload(UpdateIosBundleConfigurationBody).addSuccess(IosBundleConfiguration).annotateContext(OpenApi.annotations({
|
|
1441
1508
|
title: "Update iOS bundle configuration",
|
|
1442
1509
|
description: "Change the credentials bound to an iOS bundle configuration"
|
|
1443
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/ios-bundle-configurations/${idParam$
|
|
1510
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/ios-bundle-configurations/${idParam$4}`.addSuccess(DeleteIosBundleConfigurationResult).annotateContext(OpenApi.annotations({
|
|
1444
1511
|
title: "Delete iOS bundle configuration",
|
|
1445
1512
|
description: "Remove an iOS bundle configuration binding"
|
|
1446
1513
|
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -1513,7 +1580,7 @@ const DeleteProjectResult = Schema.Struct({ deleted: Schema.Number });
|
|
|
1513
1580
|
|
|
1514
1581
|
//#endregion
|
|
1515
1582
|
//#region ../../packages/api/src/groups/projects.ts
|
|
1516
|
-
const idParam$
|
|
1583
|
+
const idParam$3 = HttpApiSchema.param("id", Schema.String);
|
|
1517
1584
|
const slugParam = HttpApiSchema.param("slug", Schema.String);
|
|
1518
1585
|
var ProjectsGroup = class extends HttpApiGroup.make("projects").add(HttpApiEndpoint.post("create", "/api/projects").setPayload(CreateProjectBody).addSuccess(Project, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
1519
1586
|
title: "Create project",
|
|
@@ -1526,16 +1593,16 @@ var ProjectsGroup = class extends HttpApiGroup.make("projects").add(HttpApiEndpo
|
|
|
1526
1593
|
})).annotateContext(OpenApi.annotations({
|
|
1527
1594
|
title: "List projects",
|
|
1528
1595
|
description: "List all projects in the caller's active organization"
|
|
1529
|
-
}))).add(HttpApiEndpoint.get("get")`/api/projects/${idParam$
|
|
1596
|
+
}))).add(HttpApiEndpoint.get("get")`/api/projects/${idParam$3}`.addSuccess(Project).annotateContext(OpenApi.annotations({
|
|
1530
1597
|
title: "Get project",
|
|
1531
1598
|
description: "Get a single project by ID"
|
|
1532
1599
|
}))).add(HttpApiEndpoint.get("getBySlug")`/api/projects/by-slug/${slugParam}`.addSuccess(Project).annotateContext(OpenApi.annotations({
|
|
1533
1600
|
title: "Get project by slug",
|
|
1534
1601
|
description: "Get a single project by slug within the caller's active organization"
|
|
1535
|
-
}))).add(HttpApiEndpoint.patch("rename")`/api/projects/${idParam$
|
|
1602
|
+
}))).add(HttpApiEndpoint.patch("rename")`/api/projects/${idParam$3}`.setPayload(UpdateProjectBody).addSuccess(Project).annotateContext(OpenApi.annotations({
|
|
1536
1603
|
title: "Rename project",
|
|
1537
1604
|
description: "Rename a project"
|
|
1538
|
-
}))).add(HttpApiEndpoint.del("delete")`/api/projects/${idParam$
|
|
1605
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/projects/${idParam$3}`.addSuccess(DeleteProjectResult).annotateContext(OpenApi.annotations({
|
|
1539
1606
|
title: "Delete project",
|
|
1540
1607
|
description: "Delete a project and all its branches, channels, and updates"
|
|
1541
1608
|
}))).addError(NotFound).addError(Conflict).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
@@ -1543,6 +1610,128 @@ var ProjectsGroup = class extends HttpApiGroup.make("projects").add(HttpApiEndpo
|
|
|
1543
1610
|
description: "Project management endpoints"
|
|
1544
1611
|
})) {};
|
|
1545
1612
|
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region ../../packages/api/src/domain/submission.ts
|
|
1615
|
+
const SubmissionStatus = Schema.Literal("AWAITING_BUILD", "IN_QUEUE", "IN_PROGRESS", "FINISHED", "ERRORED", "CANCELED");
|
|
1616
|
+
const SubmissionArchiveSource = Schema.Literal("build", "path", "url");
|
|
1617
|
+
const AndroidReleaseStatus = Schema.Literal("completed", "draft", "halted", "inProgress");
|
|
1618
|
+
const AndroidTrack = Schema.String.pipe(Schema.minLength(1), Schema.maxLength(100));
|
|
1619
|
+
const Rollout = Schema.Number.pipe(Schema.greaterThan(0), Schema.lessThanOrEqualTo(1));
|
|
1620
|
+
var IosSubmissionConfig = class extends Schema.Class("IosSubmissionConfig")({
|
|
1621
|
+
appleId: Schema.NullOr(Schema.String),
|
|
1622
|
+
ascAppId: Schema.NullOr(Schema.String),
|
|
1623
|
+
appleTeamId: Schema.NullOr(Schema.String),
|
|
1624
|
+
sku: Schema.NullOr(Schema.String),
|
|
1625
|
+
language: Schema.String,
|
|
1626
|
+
companyName: Schema.NullOr(Schema.String),
|
|
1627
|
+
appName: Schema.NullOr(Schema.String),
|
|
1628
|
+
bundleIdentifier: Schema.String,
|
|
1629
|
+
ascApiKeyId: Schema.NullOr(Id),
|
|
1630
|
+
groups: Schema.Array(Schema.String),
|
|
1631
|
+
whatToTest: Schema.NullOr(Schema.String)
|
|
1632
|
+
}) {};
|
|
1633
|
+
var AndroidSubmissionConfig = class extends Schema.Class("AndroidSubmissionConfig")({
|
|
1634
|
+
applicationId: Schema.String,
|
|
1635
|
+
track: Schema.String,
|
|
1636
|
+
releaseStatus: AndroidReleaseStatus,
|
|
1637
|
+
changesNotSentForReview: Schema.Boolean,
|
|
1638
|
+
rollout: Schema.NullOr(Schema.Number),
|
|
1639
|
+
googleServiceAccountKeyId: Schema.NullOr(Id)
|
|
1640
|
+
}) {};
|
|
1641
|
+
var Submission = class extends Schema.Class("Submission")({
|
|
1642
|
+
id: Id,
|
|
1643
|
+
organizationId: Id,
|
|
1644
|
+
projectId: Id,
|
|
1645
|
+
platform: Platform,
|
|
1646
|
+
profileName: Schema.String,
|
|
1647
|
+
status: SubmissionStatus,
|
|
1648
|
+
archiveSource: SubmissionArchiveSource,
|
|
1649
|
+
buildId: Schema.NullOr(Id),
|
|
1650
|
+
archiveUrl: Schema.NullOr(Schema.String),
|
|
1651
|
+
iosConfig: Schema.NullOr(IosSubmissionConfig),
|
|
1652
|
+
androidConfig: Schema.NullOr(AndroidSubmissionConfig),
|
|
1653
|
+
errorCode: Schema.NullOr(Schema.String),
|
|
1654
|
+
errorMessage: Schema.NullOr(Schema.String),
|
|
1655
|
+
logFiles: Schema.Array(Schema.String),
|
|
1656
|
+
initiatingUserId: Schema.NullOr(Schema.String),
|
|
1657
|
+
queuedAt: Schema.NullOr(DateTimeString),
|
|
1658
|
+
startedAt: Schema.NullOr(DateTimeString),
|
|
1659
|
+
completedAt: Schema.NullOr(DateTimeString),
|
|
1660
|
+
createdAt: DateTimeString,
|
|
1661
|
+
updatedAt: DateTimeString
|
|
1662
|
+
}) {};
|
|
1663
|
+
const CreateIosSubmissionBody = Schema.Struct({
|
|
1664
|
+
appleId: Schema.optional(Schema.String),
|
|
1665
|
+
ascAppId: Schema.optional(Schema.String),
|
|
1666
|
+
appleTeamId: Schema.optional(Schema.String),
|
|
1667
|
+
sku: Schema.optional(Schema.String),
|
|
1668
|
+
language: Schema.optional(Schema.String),
|
|
1669
|
+
companyName: Schema.optional(Schema.String),
|
|
1670
|
+
appName: Schema.optional(Schema.String),
|
|
1671
|
+
bundleIdentifier: BundleIdentifier,
|
|
1672
|
+
ascApiKeyId: Schema.optional(Id),
|
|
1673
|
+
groups: Schema.optional(Schema.Array(Schema.String)),
|
|
1674
|
+
whatToTest: Schema.optional(Schema.String)
|
|
1675
|
+
});
|
|
1676
|
+
const CreateAndroidSubmissionBody = Schema.Struct({
|
|
1677
|
+
applicationId: Schema.String,
|
|
1678
|
+
track: Schema.optional(AndroidTrack),
|
|
1679
|
+
releaseStatus: Schema.optional(AndroidReleaseStatus),
|
|
1680
|
+
changesNotSentForReview: Schema.optional(Schema.Boolean),
|
|
1681
|
+
rollout: Schema.optional(Rollout),
|
|
1682
|
+
googleServiceAccountKeyId: Schema.optional(Id)
|
|
1683
|
+
});
|
|
1684
|
+
const CreateSubmissionBody = Schema.Struct({
|
|
1685
|
+
platform: Platform,
|
|
1686
|
+
profileName: Schema.optional(Schema.String),
|
|
1687
|
+
archiveSource: SubmissionArchiveSource,
|
|
1688
|
+
buildId: Schema.optional(Id),
|
|
1689
|
+
archiveUrl: Schema.optional(Schema.String),
|
|
1690
|
+
iosConfig: Schema.optional(CreateIosSubmissionBody),
|
|
1691
|
+
androidConfig: Schema.optional(CreateAndroidSubmissionBody)
|
|
1692
|
+
});
|
|
1693
|
+
const UpdateSubmissionStatusBody = Schema.Struct({
|
|
1694
|
+
status: SubmissionStatus,
|
|
1695
|
+
errorCode: Schema.optional(Schema.NullOr(Schema.String)),
|
|
1696
|
+
errorMessage: Schema.optional(Schema.NullOr(Schema.String)),
|
|
1697
|
+
logFiles: Schema.optional(Schema.Array(Schema.String))
|
|
1698
|
+
});
|
|
1699
|
+
const CancelSubmissionResult = Schema.Struct({ canceled: Schema.Boolean });
|
|
1700
|
+
const DeleteSubmissionResult = Schema.Struct({ deleted: Schema.Number });
|
|
1701
|
+
|
|
1702
|
+
//#endregion
|
|
1703
|
+
//#region ../../packages/api/src/groups/submissions.ts
|
|
1704
|
+
const idParam$2 = HttpApiSchema.param("id", Schema.String);
|
|
1705
|
+
const projectIdParam$1 = HttpApiSchema.param("projectId", Schema.String);
|
|
1706
|
+
const ListParams = Schema.Struct({
|
|
1707
|
+
status: Schema.optional(SubmissionStatus),
|
|
1708
|
+
platform: Schema.optional(Platform),
|
|
1709
|
+
profile: Schema.optional(Schema.String),
|
|
1710
|
+
buildId: Schema.optional(Schema.String)
|
|
1711
|
+
});
|
|
1712
|
+
var SubmissionsGroup = class extends HttpApiGroup.make("submissions").add(HttpApiEndpoint.get("list")`/api/projects/${projectIdParam$1}/submissions`.setUrlParams(ListParams).addSuccess(Schema.Struct({ items: Schema.Array(Submission) })).annotateContext(OpenApi.annotations({
|
|
1713
|
+
title: "List submissions",
|
|
1714
|
+
description: "List store submissions for a project with optional filters"
|
|
1715
|
+
}))).add(HttpApiEndpoint.post("create")`/api/projects/${projectIdParam$1}/submissions`.setPayload(CreateSubmissionBody).addSuccess(Submission, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
1716
|
+
title: "Create submission",
|
|
1717
|
+
description: "Start a store submission for a build / IPA / AAB"
|
|
1718
|
+
}))).add(HttpApiEndpoint.get("get")`/api/submissions/${idParam$2}`.addSuccess(Submission).annotateContext(OpenApi.annotations({
|
|
1719
|
+
title: "Get submission",
|
|
1720
|
+
description: "Get a submission by id"
|
|
1721
|
+
}))).add(HttpApiEndpoint.patch("updateStatus")`/api/submissions/${idParam$2}/status`.setPayload(UpdateSubmissionStatusBody).addSuccess(Submission).annotateContext(OpenApi.annotations({
|
|
1722
|
+
title: "Update submission status",
|
|
1723
|
+
description: "Patch status / error / logFiles. CLI uses this for iOS submissions."
|
|
1724
|
+
}))).add(HttpApiEndpoint.post("cancel")`/api/submissions/${idParam$2}/cancel`.addSuccess(CancelSubmissionResult).annotateContext(OpenApi.annotations({
|
|
1725
|
+
title: "Cancel submission",
|
|
1726
|
+
description: "Cancel an AWAITING_BUILD / IN_QUEUE submission"
|
|
1727
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/submissions/${idParam$2}`.addSuccess(DeleteSubmissionResult).annotateContext(OpenApi.annotations({
|
|
1728
|
+
title: "Delete submission",
|
|
1729
|
+
description: "Delete a terminal submission record"
|
|
1730
|
+
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
1731
|
+
title: "Submissions",
|
|
1732
|
+
description: "Store-submission lifecycle (App Store + Google Play)"
|
|
1733
|
+
})) {};
|
|
1734
|
+
|
|
1546
1735
|
//#endregion
|
|
1547
1736
|
//#region ../../packages/api/src/groups/updates.ts
|
|
1548
1737
|
const idParam$1 = HttpApiSchema.param("id", Schema.String);
|
|
@@ -1644,7 +1833,7 @@ var WebhooksGroup = class extends HttpApiGroup.make("webhooks").add(HttpApiEndpo
|
|
|
1644
1833
|
|
|
1645
1834
|
//#endregion
|
|
1646
1835
|
//#region ../../packages/api/src/api.ts
|
|
1647
|
-
var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(EnvVarsGroup).add(FingerprintsGroup).add(AuditLogsGroup).add(DevicesGroup).add(AppleTeamsGroup).add(AppleDistributionCertificatesGroup).add(ApplePushKeysGroup).add(AscApiKeysGroup).add(AppleProvisioningProfilesGroup).add(GoogleServiceAccountKeysGroup).add(IosBundleConfigurationsGroup).add(AndroidApplicationIdentifiersGroup).add(AndroidUploadKeystoresGroup).add(AndroidBuildCredentialsGroup).add(BuildCredentialsGroup).add(MeGroup).add(WebhooksGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
|
|
1836
|
+
var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(EnvVarsGroup).add(FingerprintsGroup).add(AuditLogsGroup).add(DevicesGroup).add(AppleTeamsGroup).add(AppleDistributionCertificatesGroup).add(ApplePushKeysGroup).add(AscApiKeysGroup).add(AppleProvisioningProfilesGroup).add(GoogleServiceAccountKeysGroup).add(IosBundleConfigurationsGroup).add(IosAppMetadataGroup).add(SubmissionsGroup).add(AndroidApplicationIdentifiersGroup).add(AndroidUploadKeystoresGroup).add(AndroidBuildCredentialsGroup).add(BuildCredentialsGroup).add(MeGroup).add(WebhooksGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
|
|
1648
1837
|
title: "Better Update Management API",
|
|
1649
1838
|
version: "1.0.0",
|
|
1650
1839
|
description: "Management API for OTA update publishing, deployment, and analytics"
|
|
@@ -3412,7 +3601,7 @@ const pemToPkcs8Der = (pem) => {
|
|
|
3412
3601
|
//#region src/lib/apple-asc-jwt.ts
|
|
3413
3602
|
var AppleAuthError = class extends Data.TaggedError("AppleAuthError") {};
|
|
3414
3603
|
const MAX_JWT_LIFETIME_SECONDS = 1200;
|
|
3415
|
-
const asArrayBuffer = (bytes) => {
|
|
3604
|
+
const asArrayBuffer$1 = (bytes) => {
|
|
3416
3605
|
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
3417
3606
|
new Uint8Array(buffer).set(bytes);
|
|
3418
3607
|
return buffer;
|
|
@@ -3434,7 +3623,7 @@ const signAscJwt = (credentials) => Effect.gen(function* () {
|
|
|
3434
3623
|
};
|
|
3435
3624
|
const signingInput = `${toBase64Url(new TextEncoder().encode(JSON.stringify(header)))}.${toBase64Url(new TextEncoder().encode(JSON.stringify(payload)))}`;
|
|
3436
3625
|
const key = yield* Effect.tryPromise({
|
|
3437
|
-
try: async () => crypto.subtle.importKey("pkcs8", asArrayBuffer(der), {
|
|
3626
|
+
try: async () => crypto.subtle.importKey("pkcs8", asArrayBuffer$1(der), {
|
|
3438
3627
|
name: "ECDSA",
|
|
3439
3628
|
namedCurve: "P-256"
|
|
3440
3629
|
}, false, ["sign"]),
|
|
@@ -4653,12 +4842,6 @@ const runAndroidBuild = (input) => Effect.gen(function* () {
|
|
|
4653
4842
|
const buildType = androidProfile.buildType ?? "release";
|
|
4654
4843
|
const androidDir = path.join(projectRoot, "android");
|
|
4655
4844
|
const commandEnv = yield* runtime.commandEnvironment(envVars);
|
|
4656
|
-
const credentials = input.credentialsSource === "local" ? yield* loadLocalAndroidCredentials({ projectRoot }) : yield* downloadAndroidCredentials(api, {
|
|
4657
|
-
projectId,
|
|
4658
|
-
applicationIdentifier,
|
|
4659
|
-
tempDir,
|
|
4660
|
-
buildProfile: input.profileName
|
|
4661
|
-
});
|
|
4662
4845
|
yield* runStep({
|
|
4663
4846
|
command: "bunx",
|
|
4664
4847
|
args: [
|
|
@@ -4671,21 +4854,27 @@ const runAndroidBuild = (input) => Effect.gen(function* () {
|
|
|
4671
4854
|
cwd: projectRoot,
|
|
4672
4855
|
env: commandEnv
|
|
4673
4856
|
}, "expo prebuild android");
|
|
4674
|
-
const
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4857
|
+
const gradleArgs = yield* input.skipCredentials ? Effect.succeed([]) : Effect.gen(function* () {
|
|
4858
|
+
const credentials = input.credentialsSource === "local" ? yield* loadLocalAndroidCredentials({ projectRoot }) : yield* downloadAndroidCredentials(api, {
|
|
4859
|
+
projectId,
|
|
4860
|
+
applicationIdentifier,
|
|
4861
|
+
tempDir,
|
|
4862
|
+
buildProfile: input.profileName
|
|
4863
|
+
});
|
|
4864
|
+
const fs = yield* FileSystem.FileSystem;
|
|
4865
|
+
const signingGradlePath = path.join(tempDir, "signing.gradle");
|
|
4866
|
+
yield* fs.writeFileString(signingGradlePath, renderSigningGradle({
|
|
4867
|
+
keystorePath: credentials.keystorePath,
|
|
4868
|
+
storePassword: credentials.storePassword,
|
|
4869
|
+
keyAlias: credentials.keyAlias,
|
|
4870
|
+
keyPassword: credentials.keyPassword
|
|
4871
|
+
}));
|
|
4872
|
+
return ["--init-script", signingGradlePath];
|
|
4873
|
+
});
|
|
4874
|
+
const taskName = gradleTaskName(format, flavor, buildType);
|
|
4682
4875
|
yield* runStep({
|
|
4683
4876
|
command: "./gradlew",
|
|
4684
|
-
args: [
|
|
4685
|
-
"--init-script",
|
|
4686
|
-
signingGradlePath,
|
|
4687
|
-
`:app:${gradleTaskName(format, flavor, buildType)}`
|
|
4688
|
-
],
|
|
4877
|
+
args: [...gradleArgs, `:app:${taskName}`],
|
|
4689
4878
|
cwd: androidDir,
|
|
4690
4879
|
env: commandEnv
|
|
4691
4880
|
}, "gradlew");
|
|
@@ -6119,6 +6308,137 @@ const applyAutoIncrement = (input) => Effect.gen(function* () {
|
|
|
6119
6308
|
return bumps;
|
|
6120
6309
|
});
|
|
6121
6310
|
|
|
6311
|
+
//#endregion
|
|
6312
|
+
//#region src/lib/eas-submit-config.ts
|
|
6313
|
+
const MAX_SUBMIT_EXTENDS_DEPTH = 10;
|
|
6314
|
+
const asStringValue$1 = (value) => typeof value === "string" ? value : void 0;
|
|
6315
|
+
const asBooleanValue$1 = (value) => typeof value === "boolean" ? value : void 0;
|
|
6316
|
+
const asNumberValue = (raw) => typeof raw === "number" && Number.isFinite(raw) ? raw : void 0;
|
|
6317
|
+
const asStringArray = (raw) => {
|
|
6318
|
+
if (!Array.isArray(raw)) return;
|
|
6319
|
+
const items = raw.filter((item) => typeof item === "string");
|
|
6320
|
+
return items.length === 0 ? void 0 : items;
|
|
6321
|
+
};
|
|
6322
|
+
const asAndroidReleaseStatus = (raw) => {
|
|
6323
|
+
const value = asStringValue$1(raw);
|
|
6324
|
+
return value === "completed" || value === "draft" || value === "halted" || value === "inProgress" ? value : void 0;
|
|
6325
|
+
};
|
|
6326
|
+
const parseIosSubmitProfile = (raw) => {
|
|
6327
|
+
const record = asRecord(raw);
|
|
6328
|
+
if (!record) return;
|
|
6329
|
+
const appleId = asStringValue$1(record["appleId"]);
|
|
6330
|
+
const ascAppId = asStringValue$1(record["ascAppId"]);
|
|
6331
|
+
const appleTeamId = asStringValue$1(record["appleTeamId"]);
|
|
6332
|
+
const ascApiKeyPath = asStringValue$1(record["ascApiKeyPath"]);
|
|
6333
|
+
const ascApiKeyId = asStringValue$1(record["ascApiKeyId"]);
|
|
6334
|
+
const ascApiKeyIssuerId = asStringValue$1(record["ascApiKeyIssuerId"]);
|
|
6335
|
+
const sku = asStringValue$1(record["sku"]);
|
|
6336
|
+
const language = asStringValue$1(record["language"]);
|
|
6337
|
+
const companyName = asStringValue$1(record["companyName"]);
|
|
6338
|
+
const appName = asStringValue$1(record["appName"]);
|
|
6339
|
+
const bundleIdentifier = asStringValue$1(record["bundleIdentifier"]);
|
|
6340
|
+
const metadataPath = asStringValue$1(record["metadataPath"]);
|
|
6341
|
+
const groups = asStringArray(record["groups"]);
|
|
6342
|
+
return {
|
|
6343
|
+
...appleId === void 0 ? {} : { appleId },
|
|
6344
|
+
...ascAppId === void 0 ? {} : { ascAppId },
|
|
6345
|
+
...appleTeamId === void 0 ? {} : { appleTeamId },
|
|
6346
|
+
...ascApiKeyPath === void 0 ? {} : { ascApiKeyPath },
|
|
6347
|
+
...ascApiKeyId === void 0 ? {} : { ascApiKeyId },
|
|
6348
|
+
...ascApiKeyIssuerId === void 0 ? {} : { ascApiKeyIssuerId },
|
|
6349
|
+
...sku === void 0 ? {} : { sku },
|
|
6350
|
+
...language === void 0 ? {} : { language },
|
|
6351
|
+
...companyName === void 0 ? {} : { companyName },
|
|
6352
|
+
...appName === void 0 ? {} : { appName },
|
|
6353
|
+
...bundleIdentifier === void 0 ? {} : { bundleIdentifier },
|
|
6354
|
+
...metadataPath === void 0 ? {} : { metadataPath },
|
|
6355
|
+
...groups === void 0 ? {} : { groups }
|
|
6356
|
+
};
|
|
6357
|
+
};
|
|
6358
|
+
const parseAndroidSubmitProfile = (raw) => {
|
|
6359
|
+
const record = asRecord(raw);
|
|
6360
|
+
if (!record) return;
|
|
6361
|
+
const serviceAccountKeyPath = asStringValue$1(record["serviceAccountKeyPath"]);
|
|
6362
|
+
const serviceAccountKeyId = asStringValue$1(record["serviceAccountKeyId"]);
|
|
6363
|
+
const track = asStringValue$1(record["track"]);
|
|
6364
|
+
const releaseStatus = asAndroidReleaseStatus(record["releaseStatus"]);
|
|
6365
|
+
const changesNotSentForReview = asBooleanValue$1(record["changesNotSentForReview"]);
|
|
6366
|
+
const rollout = asNumberValue(record["rollout"]);
|
|
6367
|
+
const applicationId = asStringValue$1(record["applicationId"]);
|
|
6368
|
+
return {
|
|
6369
|
+
...serviceAccountKeyPath === void 0 ? {} : { serviceAccountKeyPath },
|
|
6370
|
+
...serviceAccountKeyId === void 0 ? {} : { serviceAccountKeyId },
|
|
6371
|
+
...track === void 0 ? {} : { track },
|
|
6372
|
+
...releaseStatus === void 0 ? {} : { releaseStatus },
|
|
6373
|
+
...changesNotSentForReview === void 0 ? {} : { changesNotSentForReview },
|
|
6374
|
+
...rollout === void 0 ? {} : { rollout },
|
|
6375
|
+
...applicationId === void 0 ? {} : { applicationId }
|
|
6376
|
+
};
|
|
6377
|
+
};
|
|
6378
|
+
const parseSubmitProfile = (raw) => {
|
|
6379
|
+
const record = asRecord(raw);
|
|
6380
|
+
if (!record) return;
|
|
6381
|
+
const extendsName = asStringValue$1(record["extends"]);
|
|
6382
|
+
const ios = parseIosSubmitProfile(record["ios"]);
|
|
6383
|
+
const android = parseAndroidSubmitProfile(record["android"]);
|
|
6384
|
+
return {
|
|
6385
|
+
...extendsName === void 0 ? {} : { extends: extendsName },
|
|
6386
|
+
...ios === void 0 ? {} : { ios },
|
|
6387
|
+
...android === void 0 ? {} : { android }
|
|
6388
|
+
};
|
|
6389
|
+
};
|
|
6390
|
+
const mergeIosSubmit = (base, overlay) => {
|
|
6391
|
+
if (!base) return overlay;
|
|
6392
|
+
if (!overlay) return base;
|
|
6393
|
+
return {
|
|
6394
|
+
...base,
|
|
6395
|
+
...overlay
|
|
6396
|
+
};
|
|
6397
|
+
};
|
|
6398
|
+
const mergeAndroidSubmit = (base, overlay) => {
|
|
6399
|
+
if (!base) return overlay;
|
|
6400
|
+
if (!overlay) return base;
|
|
6401
|
+
return {
|
|
6402
|
+
...base,
|
|
6403
|
+
...overlay
|
|
6404
|
+
};
|
|
6405
|
+
};
|
|
6406
|
+
const mergeSubmitProfile = (base, overlay) => {
|
|
6407
|
+
const ios = mergeIosSubmit(base.ios, overlay.ios);
|
|
6408
|
+
const android = mergeAndroidSubmit(base.android, overlay.android);
|
|
6409
|
+
return {
|
|
6410
|
+
...overlay.extends === void 0 ? {} : { extends: overlay.extends },
|
|
6411
|
+
...ios === void 0 ? {} : { ios },
|
|
6412
|
+
...android === void 0 ? {} : { android }
|
|
6413
|
+
};
|
|
6414
|
+
};
|
|
6415
|
+
const collectSubmitExtendsChain = (profiles, profileName) => Effect.gen(function* () {
|
|
6416
|
+
const chain = [];
|
|
6417
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6418
|
+
let current = profileName;
|
|
6419
|
+
let depth = 0;
|
|
6420
|
+
while (current !== void 0) {
|
|
6421
|
+
if (visited.has(current)) return yield* new BuildProfileError({ message: `Cycle detected in eas.json submit.${profileName} extends chain at "${current}".` });
|
|
6422
|
+
visited.add(current);
|
|
6423
|
+
const profile = profiles[current];
|
|
6424
|
+
if (!profile) return yield* new BuildProfileError({ message: current === profileName ? `Submit profile "${profileName}" not found in eas.json.` : `Submit profile "${profileName}" extends missing profile "${current}".` });
|
|
6425
|
+
chain.unshift(profile);
|
|
6426
|
+
current = profile.extends;
|
|
6427
|
+
depth += 1;
|
|
6428
|
+
if (depth > MAX_SUBMIT_EXTENDS_DEPTH) return yield* new BuildProfileError({ message: `Too many "extends" levels (max ${String(MAX_SUBMIT_EXTENDS_DEPTH)}) in eas.json submit.${profileName}.` });
|
|
6429
|
+
}
|
|
6430
|
+
return chain;
|
|
6431
|
+
});
|
|
6432
|
+
const stripSubmitExtends = (profile) => {
|
|
6433
|
+
if (profile.extends === void 0) return profile;
|
|
6434
|
+
const { extends: _omit, ...rest } = profile;
|
|
6435
|
+
return rest;
|
|
6436
|
+
};
|
|
6437
|
+
const resolveEasSubmitProfile = (profiles, profileName) => Effect.gen(function* () {
|
|
6438
|
+
if (!profiles) return yield* new BuildProfileError({ message: "eas.json has no \"submit\" section. Add at least one submit profile." });
|
|
6439
|
+
return stripSubmitExtends((yield* collectSubmitExtendsChain(profiles, profileName)).reduce((acc, next, index) => index === 0 ? next : mergeSubmitProfile(acc, next), {}));
|
|
6440
|
+
});
|
|
6441
|
+
|
|
6122
6442
|
//#endregion
|
|
6123
6443
|
//#region src/lib/eas-config.ts
|
|
6124
6444
|
const MAX_EXTENDS_DEPTH = 10;
|
|
@@ -6223,6 +6543,7 @@ const parseBuildProfile = (raw) => {
|
|
|
6223
6543
|
const android = parseAndroidProfile(record["android"]);
|
|
6224
6544
|
const credentialsSource = asCredentialsSource(record["credentialsSource"]);
|
|
6225
6545
|
const autoIncrement = asAutoIncrement(record["autoIncrement"]);
|
|
6546
|
+
const withoutCredentials = asBooleanValue(record["withoutCredentials"]);
|
|
6226
6547
|
return {
|
|
6227
6548
|
...extendsName === void 0 ? {} : { extends: extendsName },
|
|
6228
6549
|
...developmentClient === void 0 ? {} : { developmentClient },
|
|
@@ -6233,7 +6554,8 @@ const parseBuildProfile = (raw) => {
|
|
|
6233
6554
|
...ios === void 0 ? {} : { ios },
|
|
6234
6555
|
...android === void 0 ? {} : { android },
|
|
6235
6556
|
...credentialsSource === void 0 ? {} : { credentialsSource },
|
|
6236
|
-
...autoIncrement === void 0 ? {} : { autoIncrement }
|
|
6557
|
+
...autoIncrement === void 0 ? {} : { autoIncrement },
|
|
6558
|
+
...withoutCredentials === void 0 ? {} : { withoutCredentials }
|
|
6237
6559
|
};
|
|
6238
6560
|
};
|
|
6239
6561
|
const parseEasConfig = (text) => Effect.gen(function* () {
|
|
@@ -6249,9 +6571,16 @@ const parseEasConfig = (text) => Effect.gen(function* () {
|
|
|
6249
6571
|
const profile = parseBuildProfile(value);
|
|
6250
6572
|
if (profile) profiles[name] = profile;
|
|
6251
6573
|
}
|
|
6574
|
+
const submitRecord = asRecord(root["submit"]);
|
|
6575
|
+
const submit = {};
|
|
6576
|
+
if (submitRecord) for (const [name, value] of Object.entries(submitRecord)) {
|
|
6577
|
+
const profile = parseSubmitProfile(value);
|
|
6578
|
+
if (profile !== void 0) submit[name] = profile;
|
|
6579
|
+
}
|
|
6252
6580
|
return {
|
|
6253
6581
|
...asRecord(root["cli"]) ? { cli: parseCli(root["cli"]) } : {},
|
|
6254
|
-
build: profiles
|
|
6582
|
+
build: profiles,
|
|
6583
|
+
...Object.keys(submit).length === 0 ? {} : { submit }
|
|
6255
6584
|
};
|
|
6256
6585
|
});
|
|
6257
6586
|
const parseCli = (raw) => {
|
|
@@ -6302,6 +6631,7 @@ const mergeProfile = (base, overlay) => {
|
|
|
6302
6631
|
const environment = overlay.environment ?? base.environment;
|
|
6303
6632
|
const credentialsSource = overlay.credentialsSource ?? base.credentialsSource;
|
|
6304
6633
|
const autoIncrement = overlay.autoIncrement ?? base.autoIncrement;
|
|
6634
|
+
const withoutCredentials = overlay.withoutCredentials ?? base.withoutCredentials;
|
|
6305
6635
|
return {
|
|
6306
6636
|
...overlay.extends === void 0 ? {} : { extends: overlay.extends },
|
|
6307
6637
|
...developmentClient === void 0 ? {} : { developmentClient },
|
|
@@ -6312,7 +6642,8 @@ const mergeProfile = (base, overlay) => {
|
|
|
6312
6642
|
...ios === void 0 ? {} : { ios },
|
|
6313
6643
|
...android === void 0 ? {} : { android },
|
|
6314
6644
|
...credentialsSource === void 0 ? {} : { credentialsSource },
|
|
6315
|
-
...autoIncrement === void 0 ? {} : { autoIncrement }
|
|
6645
|
+
...autoIncrement === void 0 ? {} : { autoIncrement },
|
|
6646
|
+
...withoutCredentials === void 0 ? {} : { withoutCredentials }
|
|
6316
6647
|
};
|
|
6317
6648
|
};
|
|
6318
6649
|
const collectExtendsChain = (profiles, profileName) => Effect.gen(function* () {
|
|
@@ -6390,9 +6721,10 @@ const toIosProfile = (eas) => {
|
|
|
6390
6721
|
if (!distribution) return;
|
|
6391
6722
|
const ios = eas.ios ?? {};
|
|
6392
6723
|
const autoIncrement = resolveIosAutoIncrement(eas);
|
|
6724
|
+
const buildConfiguration = ios.buildConfiguration ?? (eas.developmentClient === true ? "Debug" : void 0);
|
|
6393
6725
|
return {
|
|
6394
6726
|
distribution,
|
|
6395
|
-
...
|
|
6727
|
+
...buildConfiguration === void 0 ? {} : { buildConfiguration },
|
|
6396
6728
|
...ios.scheme === void 0 ? {} : { scheme: ios.scheme },
|
|
6397
6729
|
...ios.simulator === void 0 ? {} : { simulator: ios.simulator },
|
|
6398
6730
|
...autoIncrement === void 0 ? {} : { autoIncrement }
|
|
@@ -6405,10 +6737,11 @@ const toAndroidProfile = (eas) => {
|
|
|
6405
6737
|
const android = eas.android ?? {};
|
|
6406
6738
|
const distribution = deriveAndroidDistribution(eas, format);
|
|
6407
6739
|
const autoIncrement = resolveAndroidAutoIncrement(eas);
|
|
6740
|
+
const buildType = android.buildType ?? (eas.developmentClient === true ? "debug" : void 0);
|
|
6408
6741
|
return {
|
|
6409
6742
|
format,
|
|
6410
6743
|
distribution,
|
|
6411
|
-
...
|
|
6744
|
+
...buildType === void 0 ? {} : { buildType },
|
|
6412
6745
|
...android.flavor === void 0 ? {} : { flavor: android.flavor },
|
|
6413
6746
|
...android.gradleCommand === void 0 ? {} : { gradleCommand: android.gradleCommand },
|
|
6414
6747
|
...autoIncrement === void 0 ? {} : { autoIncrement }
|
|
@@ -6424,7 +6757,9 @@ const fromEasProfile = (eas, profileName) => {
|
|
|
6424
6757
|
...eas.env === void 0 ? {} : { env: eas.env },
|
|
6425
6758
|
...ios === void 0 ? {} : { ios },
|
|
6426
6759
|
...android === void 0 ? {} : { android },
|
|
6427
|
-
...eas.credentialsSource === void 0 ? {} : { credentialsSource: eas.credentialsSource }
|
|
6760
|
+
...eas.credentialsSource === void 0 ? {} : { credentialsSource: eas.credentialsSource },
|
|
6761
|
+
...eas.developmentClient === void 0 ? {} : { developmentClient: eas.developmentClient },
|
|
6762
|
+
...eas.withoutCredentials === void 0 ? {} : { withoutCredentials: eas.withoutCredentials }
|
|
6428
6763
|
};
|
|
6429
6764
|
};
|
|
6430
6765
|
const readBuildProfile = (projectRoot, profileName) => Effect.gen(function* () {
|
|
@@ -6467,6 +6802,51 @@ const clearBuildCaches = (projectRoot) => Effect.gen(function* () {
|
|
|
6467
6802
|
if (removed.length > 0) yield* Console.error(`Cleared caches: ${removed.join(", ")}`);
|
|
6468
6803
|
});
|
|
6469
6804
|
|
|
6805
|
+
//#endregion
|
|
6806
|
+
//#region src/lib/dev-client-check.ts
|
|
6807
|
+
const readDeps = (filePath) => Effect.gen(function* () {
|
|
6808
|
+
const text = yield* (yield* FileSystem.FileSystem).readFileString(filePath).pipe(Effect.option, Effect.catchAllDefect(() => Effect.succeedNone));
|
|
6809
|
+
if (text._tag === "None") return;
|
|
6810
|
+
const parsed = yield* Effect.try({
|
|
6811
|
+
try: () => JSON.parse(text.value),
|
|
6812
|
+
catch: () => void 0
|
|
6813
|
+
}).pipe(Effect.option);
|
|
6814
|
+
if (parsed._tag === "None") return;
|
|
6815
|
+
const root = asRecord(parsed.value);
|
|
6816
|
+
if (!root) return;
|
|
6817
|
+
const dependencies = asRecord(root["dependencies"]);
|
|
6818
|
+
const devDependencies = asRecord(root["devDependencies"]);
|
|
6819
|
+
return {
|
|
6820
|
+
...dependencies === void 0 ? {} : { dependencies },
|
|
6821
|
+
...devDependencies === void 0 ? {} : { devDependencies }
|
|
6822
|
+
};
|
|
6823
|
+
});
|
|
6824
|
+
const hasExpoDevClient = (deps) => {
|
|
6825
|
+
if (!deps) return false;
|
|
6826
|
+
return Object.hasOwn(deps.dependencies ?? {}, "expo-dev-client") || Object.hasOwn(deps.devDependencies ?? {}, "expo-dev-client");
|
|
6827
|
+
};
|
|
6828
|
+
/**
|
|
6829
|
+
* Read `<projectRoot>/package.json` and report whether `expo-dev-client` is
|
|
6830
|
+
* declared (either in `dependencies` or `devDependencies`). Returns `false`
|
|
6831
|
+
* when the file is missing or unparseable — callers treat that as "not
|
|
6832
|
+
* installed". Does not walk monorepo workspaces: `expo-dev-client` is an
|
|
6833
|
+
* app-level dep in every Expo template and root-hoisting native deps is
|
|
6834
|
+
* unusual.
|
|
6835
|
+
*/
|
|
6836
|
+
const hasDevClientInstalled = (projectRoot) => Effect.gen(function* () {
|
|
6837
|
+
return hasExpoDevClient(yield* readDeps(path.join(projectRoot, "package.json")).pipe(Effect.orElseSucceed(() => void 0)));
|
|
6838
|
+
});
|
|
6839
|
+
/**
|
|
6840
|
+
* Verify `expo-dev-client` is installed when the profile sets
|
|
6841
|
+
* `developmentClient: true`. Without it the built binary boots straight into
|
|
6842
|
+
* the regular app (no launcher, no Metro switcher) and the user cannot connect
|
|
6843
|
+
* a dev server. EAS warns and proceeds; we mirror that — warn, do not block.
|
|
6844
|
+
*/
|
|
6845
|
+
const warnIfDevClientMissing = (projectRoot) => Effect.gen(function* () {
|
|
6846
|
+
if (yield* hasDevClientInstalled(projectRoot)) return;
|
|
6847
|
+
yield* printWarn("expo-dev-client is not in dependencies. The built artifact will boot the regular app without a Metro launcher. Install it with `bunx expo install expo-dev-client` and rebuild.");
|
|
6848
|
+
});
|
|
6849
|
+
|
|
6470
6850
|
//#endregion
|
|
6471
6851
|
//#region src/lib/env-exporter.ts
|
|
6472
6852
|
const coerceEnvironment = (raw) => raw === "development" || raw === "preview" || raw === "production" ? raw : void 0;
|
|
@@ -6587,7 +6967,7 @@ const unquote = (input) => input.startsWith("\"") && input.endsWith("\"") ? inpu
|
|
|
6587
6967
|
|
|
6588
6968
|
//#endregion
|
|
6589
6969
|
//#region src/lib/platform-detect.ts
|
|
6590
|
-
const PLATFORMS = ["ios", "android"];
|
|
6970
|
+
const PLATFORMS$1 = ["ios", "android"];
|
|
6591
6971
|
const inferPlatforms = (config) => {
|
|
6592
6972
|
const fromConfig = config["platforms"];
|
|
6593
6973
|
if (Array.isArray(fromConfig)) return fromConfig.filter((entry) => entry === "ios" || entry === "android");
|
|
@@ -6612,7 +6992,7 @@ const detectPlatform = (explicit, config) => Effect.gen(function* () {
|
|
|
6612
6992
|
return only;
|
|
6613
6993
|
}
|
|
6614
6994
|
if (!(yield* InteractiveMode).allow) return yield* new BuildProfileError({ message: `Multiple platforms detected (${candidates.join(", ")}). Pass --platform explicitly when running non-interactively.` });
|
|
6615
|
-
return yield* promptSelect("Which platform to build?", PLATFORMS.filter((entry) => candidates.includes(entry)).map((entry) => ({
|
|
6995
|
+
return yield* promptSelect("Which platform to build?", PLATFORMS$1.filter((entry) => candidates.includes(entry)).map((entry) => ({
|
|
6616
6996
|
value: entry,
|
|
6617
6997
|
label: entry
|
|
6618
6998
|
})));
|
|
@@ -6620,7 +7000,7 @@ const detectPlatform = (explicit, config) => Effect.gen(function* () {
|
|
|
6620
7000
|
|
|
6621
7001
|
//#endregion
|
|
6622
7002
|
//#region src/lib/project-staging.ts
|
|
6623
|
-
const execFileAsync = promisify(execFile);
|
|
7003
|
+
const execFileAsync$1 = promisify(execFile);
|
|
6624
7004
|
const LOCKFILES = [
|
|
6625
7005
|
["bun.lock", "bun"],
|
|
6626
7006
|
["bun.lockb", "bun"],
|
|
@@ -6716,7 +7096,7 @@ const copyProjectTree = (params) => Effect.tryPromise({
|
|
|
6716
7096
|
* they exist only so `git rev-parse` succeeds during postinstall.
|
|
6717
7097
|
*/
|
|
6718
7098
|
const initGitRepo = (stagingRoot) => Effect.tryPromise({
|
|
6719
|
-
try: async () => execFileAsync("git", [
|
|
7099
|
+
try: async () => execFileAsync$1("git", [
|
|
6720
7100
|
"init",
|
|
6721
7101
|
"-q",
|
|
6722
7102
|
stagingRoot
|
|
@@ -6799,8 +7179,566 @@ const resolveRuntimeVersion = ({ raw, appVersion, projectRoot }) => Effect.gen(f
|
|
|
6799
7179
|
return yield* new RuntimeVersionError({ message: `Unsupported runtimeVersion policy "${policy}". Use a static string, "appVersion", or "fingerprint".` });
|
|
6800
7180
|
});
|
|
6801
7181
|
|
|
7182
|
+
//#endregion
|
|
7183
|
+
//#region src/lib/google-play.ts
|
|
7184
|
+
var GooglePlayAuthError = class extends Schema.TaggedError()("GooglePlayAuthError", {
|
|
7185
|
+
message: Schema.String,
|
|
7186
|
+
cause: Schema.optional(Schema.Unknown)
|
|
7187
|
+
}) {};
|
|
7188
|
+
var GooglePlayApiError = class extends Schema.TaggedError()("GooglePlayApiError", {
|
|
7189
|
+
message: Schema.String,
|
|
7190
|
+
httpStatus: Schema.optional(Schema.Number),
|
|
7191
|
+
cause: Schema.optional(Schema.Unknown)
|
|
7192
|
+
}) {};
|
|
7193
|
+
const ANDROID_PUBLISHER_SCOPE = "https://www.googleapis.com/auth/androidpublisher";
|
|
7194
|
+
const GOOGLE_OAUTH_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
7195
|
+
const ANDROID_PUBLISHER_BASE = "https://androidpublisher.googleapis.com";
|
|
7196
|
+
const ANDROID_UPLOAD_BASE = "https://androidpublisher.googleapis.com/upload";
|
|
7197
|
+
const ServiceAccountJsonSchema = Schema.Struct({
|
|
7198
|
+
type: Schema.String,
|
|
7199
|
+
client_email: Schema.String,
|
|
7200
|
+
private_key: Schema.String,
|
|
7201
|
+
token_uri: Schema.optional(Schema.String)
|
|
7202
|
+
});
|
|
7203
|
+
const TokenResponseSchema = Schema.Struct({
|
|
7204
|
+
access_token: Schema.String,
|
|
7205
|
+
expires_in: Schema.optional(Schema.Number)
|
|
7206
|
+
});
|
|
7207
|
+
const stripPemHeaders = (pem) => pem.replace(/-----BEGIN [A-Z ]+-----/u, "").replace(/-----END [A-Z ]+-----/u, "").replaceAll(/\s+/gu, "");
|
|
7208
|
+
const asArrayBuffer = (bytes) => {
|
|
7209
|
+
const buffer = new ArrayBuffer(bytes.byteLength);
|
|
7210
|
+
new Uint8Array(buffer).set(bytes);
|
|
7211
|
+
return buffer;
|
|
7212
|
+
};
|
|
7213
|
+
const importPrivateKey = (pem) => Effect.tryPromise({
|
|
7214
|
+
try: async () => {
|
|
7215
|
+
const pkcs8 = fromBase64(stripPemHeaders(pem));
|
|
7216
|
+
return crypto.subtle.importKey("pkcs8", asArrayBuffer(pkcs8), {
|
|
7217
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
7218
|
+
hash: "SHA-256"
|
|
7219
|
+
}, false, ["sign"]);
|
|
7220
|
+
},
|
|
7221
|
+
catch: (cause) => new GooglePlayAuthError({
|
|
7222
|
+
message: "Failed to import service account private key",
|
|
7223
|
+
cause
|
|
7224
|
+
})
|
|
7225
|
+
});
|
|
7226
|
+
const buildJwtAssertion = (params) => {
|
|
7227
|
+
const header = {
|
|
7228
|
+
alg: "RS256",
|
|
7229
|
+
typ: "JWT"
|
|
7230
|
+
};
|
|
7231
|
+
const claims = {
|
|
7232
|
+
iss: params.clientEmail,
|
|
7233
|
+
scope: ANDROID_PUBLISHER_SCOPE,
|
|
7234
|
+
aud: params.tokenUri,
|
|
7235
|
+
exp: params.nowSeconds + 3600,
|
|
7236
|
+
iat: params.nowSeconds
|
|
7237
|
+
};
|
|
7238
|
+
return `${toBase64Url(new TextEncoder().encode(JSON.stringify(header)))}.${toBase64Url(new TextEncoder().encode(JSON.stringify(claims)))}`;
|
|
7239
|
+
};
|
|
7240
|
+
const signJwt = (key, payload) => Effect.tryPromise({
|
|
7241
|
+
try: async () => {
|
|
7242
|
+
const signature = await crypto.subtle.sign("RSASSA-PKCS1-v1_5", key, new TextEncoder().encode(payload));
|
|
7243
|
+
return `${payload}.${toBase64Url(new Uint8Array(signature))}`;
|
|
7244
|
+
},
|
|
7245
|
+
catch: (cause) => new GooglePlayAuthError({
|
|
7246
|
+
message: "Failed to sign JWT",
|
|
7247
|
+
cause
|
|
7248
|
+
})
|
|
7249
|
+
});
|
|
7250
|
+
const postTokenRequest = (tokenUri, jwt) => Effect.tryPromise({
|
|
7251
|
+
try: async () => {
|
|
7252
|
+
const body = new URLSearchParams({
|
|
7253
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
7254
|
+
assertion: jwt
|
|
7255
|
+
});
|
|
7256
|
+
const response = await fetch(tokenUri, {
|
|
7257
|
+
method: "POST",
|
|
7258
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
7259
|
+
body
|
|
7260
|
+
});
|
|
7261
|
+
const text = await response.text();
|
|
7262
|
+
return {
|
|
7263
|
+
ok: response.ok,
|
|
7264
|
+
status: response.status,
|
|
7265
|
+
text
|
|
7266
|
+
};
|
|
7267
|
+
},
|
|
7268
|
+
catch: (cause) => new GooglePlayAuthError({
|
|
7269
|
+
message: "Failed to exchange JWT for access token",
|
|
7270
|
+
cause
|
|
7271
|
+
})
|
|
7272
|
+
});
|
|
7273
|
+
const exchangeJwtForAccessToken = (tokenUri, jwt) => Effect.gen(function* () {
|
|
7274
|
+
const result = yield* postTokenRequest(tokenUri, jwt);
|
|
7275
|
+
if (!result.ok) return yield* Effect.fail(new GooglePlayAuthError({ message: `OAuth token exchange failed: ${String(result.status)} ${result.text}` }));
|
|
7276
|
+
const json = yield* Effect.try({
|
|
7277
|
+
try: () => JSON.parse(result.text),
|
|
7278
|
+
catch: (cause) => new GooglePlayAuthError({
|
|
7279
|
+
message: "OAuth token response is not JSON",
|
|
7280
|
+
cause
|
|
7281
|
+
})
|
|
7282
|
+
});
|
|
7283
|
+
return (yield* Schema.decodeUnknown(TokenResponseSchema)(json).pipe(Effect.mapError((cause) => new GooglePlayAuthError({
|
|
7284
|
+
message: "OAuth token response missing access_token",
|
|
7285
|
+
cause
|
|
7286
|
+
})))).access_token;
|
|
7287
|
+
});
|
|
7288
|
+
const acquireGooglePlayAccessToken = (serviceAccountJson) => Effect.gen(function* () {
|
|
7289
|
+
const raw = yield* Effect.try({
|
|
7290
|
+
try: () => JSON.parse(serviceAccountJson),
|
|
7291
|
+
catch: (cause) => new GooglePlayAuthError({
|
|
7292
|
+
message: "Service account JSON is not valid JSON",
|
|
7293
|
+
cause
|
|
7294
|
+
})
|
|
7295
|
+
});
|
|
7296
|
+
const parsed = yield* Schema.decodeUnknown(ServiceAccountJsonSchema)(raw).pipe(Effect.mapError((cause) => new GooglePlayAuthError({
|
|
7297
|
+
message: "Service account JSON missing required fields (type, client_email, private_key)",
|
|
7298
|
+
cause
|
|
7299
|
+
})));
|
|
7300
|
+
if (parsed.type !== "service_account") return yield* Effect.fail(new GooglePlayAuthError({ message: `Service account JSON has wrong type: ${parsed.type}` }));
|
|
7301
|
+
const tokenUri = parsed.token_uri ?? GOOGLE_OAUTH_TOKEN_URL;
|
|
7302
|
+
return {
|
|
7303
|
+
accessToken: yield* exchangeJwtForAccessToken(tokenUri, yield* signJwt(yield* importPrivateKey(parsed.private_key), buildJwtAssertion({
|
|
7304
|
+
clientEmail: parsed.client_email,
|
|
7305
|
+
tokenUri,
|
|
7306
|
+
nowSeconds: Math.floor(Date.now() / 1e3)
|
|
7307
|
+
}))),
|
|
7308
|
+
clientEmail: parsed.client_email
|
|
7309
|
+
};
|
|
7310
|
+
});
|
|
7311
|
+
const authHeaders = (accessToken) => ({ Authorization: `Bearer ${accessToken}` });
|
|
7312
|
+
const performFetch = (params) => Effect.tryPromise({
|
|
7313
|
+
try: async () => {
|
|
7314
|
+
const init = params.body === void 0 ? {
|
|
7315
|
+
method: params.method,
|
|
7316
|
+
headers: authHeaders(params.accessToken)
|
|
7317
|
+
} : {
|
|
7318
|
+
method: params.method,
|
|
7319
|
+
headers: {
|
|
7320
|
+
...authHeaders(params.accessToken),
|
|
7321
|
+
"Content-Type": "application/json"
|
|
7322
|
+
},
|
|
7323
|
+
body: JSON.stringify(params.body)
|
|
7324
|
+
};
|
|
7325
|
+
const response = await fetch(params.url, init);
|
|
7326
|
+
const text = await response.text();
|
|
7327
|
+
return {
|
|
7328
|
+
ok: response.ok,
|
|
7329
|
+
status: response.status,
|
|
7330
|
+
text
|
|
7331
|
+
};
|
|
7332
|
+
},
|
|
7333
|
+
catch: (cause) => new GooglePlayApiError({
|
|
7334
|
+
message: `${params.label} request failed`,
|
|
7335
|
+
cause
|
|
7336
|
+
})
|
|
7337
|
+
});
|
|
7338
|
+
const callJsonRaw = (params) => Effect.gen(function* () {
|
|
7339
|
+
const result = yield* performFetch(params);
|
|
7340
|
+
if (!result.ok) return yield* Effect.fail(new GooglePlayApiError({
|
|
7341
|
+
message: `${params.label} failed: ${String(result.status)} ${result.text}`,
|
|
7342
|
+
httpStatus: result.status
|
|
7343
|
+
}));
|
|
7344
|
+
return yield* Effect.try({
|
|
7345
|
+
try: () => result.text === "" ? {} : JSON.parse(result.text),
|
|
7346
|
+
catch: (cause) => new GooglePlayApiError({
|
|
7347
|
+
message: `${params.label} response is not JSON`,
|
|
7348
|
+
cause
|
|
7349
|
+
})
|
|
7350
|
+
});
|
|
7351
|
+
});
|
|
7352
|
+
const AppEditSchema = Schema.Struct({
|
|
7353
|
+
id: Schema.String,
|
|
7354
|
+
expiryTimeSeconds: Schema.optional(Schema.String)
|
|
7355
|
+
});
|
|
7356
|
+
const insertEdit = (params) => Effect.gen(function* () {
|
|
7357
|
+
const raw = yield* callJsonRaw({
|
|
7358
|
+
url: `${ANDROID_PUBLISHER_BASE}/androidpublisher/v3/applications/${encodeURIComponent(params.packageName)}/edits`,
|
|
7359
|
+
method: "POST",
|
|
7360
|
+
accessToken: params.accessToken,
|
|
7361
|
+
body: {},
|
|
7362
|
+
label: "edits.insert"
|
|
7363
|
+
});
|
|
7364
|
+
return yield* Schema.decodeUnknown(AppEditSchema)(raw).pipe(Effect.mapError((cause) => new GooglePlayApiError({
|
|
7365
|
+
message: "edits.insert response missing id",
|
|
7366
|
+
cause
|
|
7367
|
+
})));
|
|
7368
|
+
});
|
|
7369
|
+
const UploadedBundleSchema = Schema.Struct({
|
|
7370
|
+
versionCode: Schema.Number,
|
|
7371
|
+
sha256: Schema.optional(Schema.String)
|
|
7372
|
+
});
|
|
7373
|
+
const performBundleUpload = (params) => Effect.tryPromise({
|
|
7374
|
+
try: async () => {
|
|
7375
|
+
const url = `${ANDROID_UPLOAD_BASE}/androidpublisher/v3/applications/${encodeURIComponent(params.packageName)}/edits/${encodeURIComponent(params.editId)}/bundles?uploadType=media`;
|
|
7376
|
+
const response = await fetch(url, {
|
|
7377
|
+
method: "POST",
|
|
7378
|
+
headers: {
|
|
7379
|
+
...authHeaders(params.accessToken),
|
|
7380
|
+
"Content-Type": "application/octet-stream"
|
|
7381
|
+
},
|
|
7382
|
+
body: new Uint8Array(params.aabBytes)
|
|
7383
|
+
});
|
|
7384
|
+
const text = await response.text();
|
|
7385
|
+
return {
|
|
7386
|
+
ok: response.ok,
|
|
7387
|
+
status: response.status,
|
|
7388
|
+
text
|
|
7389
|
+
};
|
|
7390
|
+
},
|
|
7391
|
+
catch: (cause) => new GooglePlayApiError({
|
|
7392
|
+
message: "edits.bundles.upload request failed",
|
|
7393
|
+
cause
|
|
7394
|
+
})
|
|
7395
|
+
});
|
|
7396
|
+
const uploadBundle = (params) => Effect.gen(function* () {
|
|
7397
|
+
const result = yield* performBundleUpload(params);
|
|
7398
|
+
if (!result.ok) return yield* Effect.fail(new GooglePlayApiError({
|
|
7399
|
+
message: `edits.bundles.upload failed: ${String(result.status)} ${result.text}`,
|
|
7400
|
+
httpStatus: result.status
|
|
7401
|
+
}));
|
|
7402
|
+
const raw = yield* Effect.try({
|
|
7403
|
+
try: () => JSON.parse(result.text),
|
|
7404
|
+
catch: (cause) => new GooglePlayApiError({
|
|
7405
|
+
message: "edits.bundles.upload response is not JSON",
|
|
7406
|
+
cause
|
|
7407
|
+
})
|
|
7408
|
+
});
|
|
7409
|
+
return yield* Schema.decodeUnknown(UploadedBundleSchema)(raw).pipe(Effect.mapError((cause) => new GooglePlayApiError({
|
|
7410
|
+
message: "Bundle upload response missing versionCode",
|
|
7411
|
+
cause
|
|
7412
|
+
})));
|
|
7413
|
+
});
|
|
7414
|
+
const updateTrack = (params) => {
|
|
7415
|
+
const release = {
|
|
7416
|
+
status: params.releaseStatus,
|
|
7417
|
+
versionCodes: [String(params.versionCode)],
|
|
7418
|
+
...params.rollout === null ? {} : { userFraction: params.rollout },
|
|
7419
|
+
...params.releaseNotes === void 0 ? {} : { releaseNotes: [{
|
|
7420
|
+
language: "en-US",
|
|
7421
|
+
text: params.releaseNotes
|
|
7422
|
+
}] }
|
|
7423
|
+
};
|
|
7424
|
+
return callJsonRaw({
|
|
7425
|
+
url: `${ANDROID_PUBLISHER_BASE}/androidpublisher/v3/applications/${encodeURIComponent(params.packageName)}/edits/${encodeURIComponent(params.editId)}/tracks/${encodeURIComponent(params.track)}`,
|
|
7426
|
+
method: "PUT",
|
|
7427
|
+
accessToken: params.accessToken,
|
|
7428
|
+
body: {
|
|
7429
|
+
track: params.track,
|
|
7430
|
+
releases: [release]
|
|
7431
|
+
},
|
|
7432
|
+
label: "edits.tracks.update"
|
|
7433
|
+
});
|
|
7434
|
+
};
|
|
7435
|
+
const commitEdit = (params) => callJsonRaw({
|
|
7436
|
+
url: `${ANDROID_PUBLISHER_BASE}/androidpublisher/v3/applications/${encodeURIComponent(params.packageName)}/edits/${encodeURIComponent(params.editId)}:commit?changesNotSentForReview=${String(params.changesNotSentForReview)}`,
|
|
7437
|
+
method: "POST",
|
|
7438
|
+
accessToken: params.accessToken,
|
|
7439
|
+
label: "edits.commit"
|
|
7440
|
+
});
|
|
7441
|
+
|
|
7442
|
+
//#endregion
|
|
7443
|
+
//#region src/lib/nullable.ts
|
|
7444
|
+
const toDbNull = (value) => value ?? null;
|
|
7445
|
+
|
|
7446
|
+
//#endregion
|
|
7447
|
+
//#region src/application/submit-flow.ts
|
|
7448
|
+
const execFileAsync = promisify(execFile);
|
|
7449
|
+
const ExecErrorSchema = Schema.Struct({
|
|
7450
|
+
code: Schema.optional(Schema.Number),
|
|
7451
|
+
stdout: Schema.optional(Schema.String),
|
|
7452
|
+
stderr: Schema.optional(Schema.String)
|
|
7453
|
+
});
|
|
7454
|
+
const runAltool = (args) => Effect.tryPromise({
|
|
7455
|
+
try: async () => {
|
|
7456
|
+
const { stdout, stderr } = await execFileAsync("xcrun", ["altool", ...args]);
|
|
7457
|
+
return {
|
|
7458
|
+
exitCode: 0,
|
|
7459
|
+
stdout,
|
|
7460
|
+
stderr
|
|
7461
|
+
};
|
|
7462
|
+
},
|
|
7463
|
+
catch: (error) => {
|
|
7464
|
+
const parsed = Schema.decodeUnknownSync(ExecErrorSchema, { onExcessProperty: "ignore" })(typeof error === "object" && error !== null ? error : {});
|
|
7465
|
+
const stdout = parsed.stdout ?? "";
|
|
7466
|
+
const stderr = parsed.stderr ?? String(error);
|
|
7467
|
+
return {
|
|
7468
|
+
exitCode: parsed.code ?? 1,
|
|
7469
|
+
stdout,
|
|
7470
|
+
stderr: stderr === "" ? String(error) : stderr
|
|
7471
|
+
};
|
|
7472
|
+
}
|
|
7473
|
+
}).pipe(Effect.catchAll((result) => Effect.succeed(result)));
|
|
7474
|
+
var CliSubmitError = class extends Schema.TaggedError()("CliSubmitError", {
|
|
7475
|
+
code: Schema.String,
|
|
7476
|
+
message: Schema.String
|
|
7477
|
+
}) {};
|
|
7478
|
+
const createSubmissionViaApi = (api, resolved) => api.submissions.create({
|
|
7479
|
+
path: { projectId: resolved.projectId },
|
|
7480
|
+
payload: {
|
|
7481
|
+
platform: resolved.platform,
|
|
7482
|
+
profileName: resolved.profileName,
|
|
7483
|
+
archiveSource: resolved.archiveSource,
|
|
7484
|
+
...resolved.buildId === void 0 ? {} : { buildId: resolved.buildId },
|
|
7485
|
+
...resolved.archiveUrl === void 0 ? {} : { archiveUrl: resolved.archiveUrl },
|
|
7486
|
+
...resolved.iosConfig === void 0 ? {} : { iosConfig: resolved.iosConfig },
|
|
7487
|
+
...resolved.androidConfig === void 0 ? {} : { androidConfig: resolved.androidConfig }
|
|
7488
|
+
}
|
|
7489
|
+
}).pipe(Effect.mapError(() => new CliSubmitError({
|
|
7490
|
+
code: "SUBMISSION_CREATE_FAILED",
|
|
7491
|
+
message: "Failed to create submission via API"
|
|
7492
|
+
})));
|
|
7493
|
+
const isTerminal = (status) => status === "FINISHED" || status === "ERRORED" || status === "CANCELED";
|
|
7494
|
+
const fetchSubmission = (api, submissionId) => api.submissions.get({ path: { id: submissionId } }).pipe(Effect.mapError(() => new CliSubmitError({
|
|
7495
|
+
code: "SUBMISSION_GET_FAILED",
|
|
7496
|
+
message: "Failed to read submission status"
|
|
7497
|
+
})));
|
|
7498
|
+
const pollSubmissionUntilTerminal = (api, submissionId, pollIntervalMs = 5e3) => Effect.iterate(void 0, {
|
|
7499
|
+
while: (state) => state === void 0 || !isTerminal(state.status),
|
|
7500
|
+
body: (state) => Effect.gen(function* () {
|
|
7501
|
+
if (state !== void 0) yield* Effect.sleep(Duration.millis(pollIntervalMs));
|
|
7502
|
+
const next = yield* fetchSubmission(api, submissionId);
|
|
7503
|
+
yield* printHuman(`status: ${next.status}`);
|
|
7504
|
+
return next;
|
|
7505
|
+
})
|
|
7506
|
+
}).pipe(Effect.flatMap((final) => final === void 0 ? Effect.fail(new CliSubmitError({
|
|
7507
|
+
code: "SUBMISSION_POLL_NO_RESULT",
|
|
7508
|
+
message: "Polling completed without producing a submission"
|
|
7509
|
+
})) : Effect.succeed(final)));
|
|
7510
|
+
const writeAscApiKeyP8 = (api, ascApiKeyId) => Effect.gen(function* () {
|
|
7511
|
+
const creds = yield* fetchAscCredentials(api, ascApiKeyId).pipe(Effect.mapError(() => new CliSubmitError({
|
|
7512
|
+
code: "SUBMISSION_ASC_KEY_FETCH_FAILED",
|
|
7513
|
+
message: `Failed to download ASC API key ${ascApiKeyId}`
|
|
7514
|
+
})));
|
|
7515
|
+
const target = path.join(tmpdir(), `better-update-submit-AuthKey_${creds.keyId}.p8`);
|
|
7516
|
+
yield* Effect.promise(async () => writeFile(target, fromBase64(creds.p8Pem)));
|
|
7517
|
+
return {
|
|
7518
|
+
p8Path: target,
|
|
7519
|
+
keyId: creds.keyId,
|
|
7520
|
+
issuerId: creds.issuerId
|
|
7521
|
+
};
|
|
7522
|
+
});
|
|
7523
|
+
const runIosAltoolUpload = (inputs) => Effect.gen(function* () {
|
|
7524
|
+
const creds = yield* writeAscApiKeyP8(inputs.api, inputs.ascApiKeyId);
|
|
7525
|
+
const apiKeyDir = path.dirname(creds.p8Path);
|
|
7526
|
+
yield* inputs.api.submissions.updateStatus({
|
|
7527
|
+
path: { id: inputs.submissionId },
|
|
7528
|
+
payload: { status: "IN_PROGRESS" }
|
|
7529
|
+
}).pipe(Effect.mapError(() => new CliSubmitError({
|
|
7530
|
+
code: "SUBMISSION_PATCH_FAILED",
|
|
7531
|
+
message: "Failed to PATCH submission status to IN_PROGRESS"
|
|
7532
|
+
})));
|
|
7533
|
+
const result = yield* runAltool([
|
|
7534
|
+
"--upload-app",
|
|
7535
|
+
"--type",
|
|
7536
|
+
"ios",
|
|
7537
|
+
"--apiKey",
|
|
7538
|
+
creds.keyId,
|
|
7539
|
+
"--apiIssuer",
|
|
7540
|
+
creds.issuerId,
|
|
7541
|
+
"--apiKeyDir",
|
|
7542
|
+
apiKeyDir,
|
|
7543
|
+
"--file",
|
|
7544
|
+
inputs.ipaPath,
|
|
7545
|
+
"--output-format",
|
|
7546
|
+
"xml"
|
|
7547
|
+
]);
|
|
7548
|
+
const terminalStatus = result.exitCode === 0 ? "FINISHED" : "ERRORED";
|
|
7549
|
+
const errorMessage = result.exitCode === 0 ? null : `xcrun altool exited ${String(result.exitCode)}: ${result.stderr}`;
|
|
7550
|
+
yield* inputs.api.submissions.updateStatus({
|
|
7551
|
+
path: { id: inputs.submissionId },
|
|
7552
|
+
payload: {
|
|
7553
|
+
status: terminalStatus,
|
|
7554
|
+
...errorMessage === null ? {} : {
|
|
7555
|
+
errorCode: "SUBMISSION_SERVICE_IOS_ALTOOL_FAILED",
|
|
7556
|
+
errorMessage
|
|
7557
|
+
}
|
|
7558
|
+
}
|
|
7559
|
+
}).pipe(Effect.mapError(() => new CliSubmitError({
|
|
7560
|
+
code: "SUBMISSION_PATCH_FAILED",
|
|
7561
|
+
message: "Failed to PATCH submission terminal status"
|
|
7562
|
+
})));
|
|
7563
|
+
return {
|
|
7564
|
+
status: terminalStatus,
|
|
7565
|
+
stdout: result.stdout,
|
|
7566
|
+
stderr: result.stderr
|
|
7567
|
+
};
|
|
7568
|
+
});
|
|
7569
|
+
const readLocalFile = (filePath, errorCode, errorMessageFmt) => Effect.tryPromise({
|
|
7570
|
+
try: async () => readFile(filePath),
|
|
7571
|
+
catch: (cause) => new CliSubmitError({
|
|
7572
|
+
code: errorCode,
|
|
7573
|
+
message: errorMessageFmt(cause)
|
|
7574
|
+
})
|
|
7575
|
+
});
|
|
7576
|
+
const fetchArchiveOverHttp = (url) => Effect.gen(function* () {
|
|
7577
|
+
const result = yield* Effect.tryPromise({
|
|
7578
|
+
try: async () => {
|
|
7579
|
+
const response = await fetch(url);
|
|
7580
|
+
const bytes = response.ok ? new Uint8Array(await response.arrayBuffer()) : null;
|
|
7581
|
+
return {
|
|
7582
|
+
ok: response.ok,
|
|
7583
|
+
status: response.status,
|
|
7584
|
+
bytes
|
|
7585
|
+
};
|
|
7586
|
+
},
|
|
7587
|
+
catch: (cause) => new CliSubmitError({
|
|
7588
|
+
code: "SUBMISSION_ARCHIVE_DOWNLOAD_FAILED",
|
|
7589
|
+
message: `Failed to download AAB from ${url}: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
7590
|
+
})
|
|
7591
|
+
});
|
|
7592
|
+
if (!result.ok || result.bytes === null) return yield* Effect.fail(new CliSubmitError({
|
|
7593
|
+
code: "SUBMISSION_ARCHIVE_DOWNLOAD_FAILED",
|
|
7594
|
+
message: `HTTP ${String(result.status)} fetching archive at ${url}`
|
|
7595
|
+
}));
|
|
7596
|
+
return result.bytes;
|
|
7597
|
+
});
|
|
7598
|
+
const readArchiveBytes = (archive) => archive.source === "path" ? Effect.map(readLocalFile(archive.value, "SUBMISSION_ARCHIVE_READ_FAILED", (cause) => `Failed to read AAB at ${archive.value}: ${cause instanceof Error ? cause.message : String(cause)}`), (buf) => new Uint8Array(buf)) : fetchArchiveOverHttp(archive.value);
|
|
7599
|
+
const fetchServiceAccountKeyById = (api, id) => api.googleServiceAccountKeys.download({ path: { id } }).pipe(Effect.mapError(() => new CliSubmitError({
|
|
7600
|
+
code: "SUBMISSION_ANDROID_SA_KEY_FETCH_FAILED",
|
|
7601
|
+
message: `Failed to download Google service account key ${id}`
|
|
7602
|
+
})), Effect.map((data) => data.json));
|
|
7603
|
+
const resolveServiceAccountJson = (params) => {
|
|
7604
|
+
if (params.serviceAccountKeyId !== void 0) return fetchServiceAccountKeyById(params.api, params.serviceAccountKeyId);
|
|
7605
|
+
if (params.serviceAccountKeyPath !== void 0) return Effect.map(readLocalFile(params.serviceAccountKeyPath, "SUBMISSION_ANDROID_SA_KEY_LOCAL_READ_FAILED", (cause) => `Failed to read service account JSON at ${String(params.serviceAccountKeyPath)}: ${cause instanceof Error ? cause.message : String(cause)}`), (buf) => new TextDecoder().decode(buf));
|
|
7606
|
+
return Effect.fail(new CliSubmitError({
|
|
7607
|
+
code: "SUBMISSION_ANDROID_SA_KEY_MISSING",
|
|
7608
|
+
message: "Android submission requires a service account key. Pass --service-account-key-id <id>, set serviceAccountKeyId in eas.json submit profile, or set serviceAccountKeyPath to a local JSON file."
|
|
7609
|
+
}));
|
|
7610
|
+
};
|
|
7611
|
+
const patchSubmissionStatus = (api, submissionId, payload) => api.submissions.updateStatus({
|
|
7612
|
+
path: { id: submissionId },
|
|
7613
|
+
payload
|
|
7614
|
+
}).pipe(Effect.mapError(() => new CliSubmitError({
|
|
7615
|
+
code: "SUBMISSION_PATCH_FAILED",
|
|
7616
|
+
message: `Failed to PATCH submission status to ${payload.status}`
|
|
7617
|
+
})));
|
|
7618
|
+
const wrapGooglePlayError = (label) => (cause) => new CliSubmitError({
|
|
7619
|
+
code: `SUBMISSION_ANDROID_${label}`,
|
|
7620
|
+
message: cause.message
|
|
7621
|
+
});
|
|
7622
|
+
const runGooglePlayPipeline = (params) => Effect.gen(function* () {
|
|
7623
|
+
const edit = yield* insertEdit({
|
|
7624
|
+
accessToken: params.accessToken,
|
|
7625
|
+
packageName: params.applicationId
|
|
7626
|
+
}).pipe(Effect.mapError(wrapGooglePlayError("EDIT_INSERT_FAILED")));
|
|
7627
|
+
const uploaded = yield* uploadBundle({
|
|
7628
|
+
accessToken: params.accessToken,
|
|
7629
|
+
packageName: params.applicationId,
|
|
7630
|
+
editId: edit.id,
|
|
7631
|
+
aabBytes: params.aab
|
|
7632
|
+
}).pipe(Effect.mapError(wrapGooglePlayError("BUNDLE_UPLOAD_FAILED")));
|
|
7633
|
+
yield* updateTrack({
|
|
7634
|
+
accessToken: params.accessToken,
|
|
7635
|
+
packageName: params.applicationId,
|
|
7636
|
+
editId: edit.id,
|
|
7637
|
+
track: params.track,
|
|
7638
|
+
releaseStatus: params.releaseStatus,
|
|
7639
|
+
versionCode: uploaded.versionCode,
|
|
7640
|
+
rollout: params.rollout
|
|
7641
|
+
}).pipe(Effect.mapError(wrapGooglePlayError("TRACK_UPDATE_FAILED")));
|
|
7642
|
+
yield* commitEdit({
|
|
7643
|
+
accessToken: params.accessToken,
|
|
7644
|
+
packageName: params.applicationId,
|
|
7645
|
+
editId: edit.id,
|
|
7646
|
+
changesNotSentForReview: params.changesNotSentForReview
|
|
7647
|
+
}).pipe(Effect.mapError(wrapGooglePlayError("COMMIT_FAILED")));
|
|
7648
|
+
return uploaded;
|
|
7649
|
+
});
|
|
7650
|
+
const runAndroidGooglePlayUpload = (inputs) => Effect.gen(function* () {
|
|
7651
|
+
const { applicationId } = inputs.androidProfile;
|
|
7652
|
+
if (applicationId === void 0) return yield* Effect.fail(new CliSubmitError({
|
|
7653
|
+
code: "SUBMISSION_ANDROID_APP_ID_MISSING",
|
|
7654
|
+
message: "Android submit profile requires applicationId — set submit.<profile>.android.applicationId in eas.json"
|
|
7655
|
+
}));
|
|
7656
|
+
const serviceAccountJson = yield* resolveServiceAccountJson({
|
|
7657
|
+
api: inputs.api,
|
|
7658
|
+
serviceAccountKeyId: inputs.serviceAccountKeyId,
|
|
7659
|
+
serviceAccountKeyPath: inputs.androidProfile.serviceAccountKeyPath
|
|
7660
|
+
});
|
|
7661
|
+
yield* patchSubmissionStatus(inputs.api, inputs.submissionId, { status: "IN_PROGRESS" });
|
|
7662
|
+
const result = yield* Effect.gen(function* () {
|
|
7663
|
+
const token = yield* acquireGooglePlayAccessToken(serviceAccountJson).pipe(Effect.mapError(wrapGooglePlayError("AUTH_FAILED")));
|
|
7664
|
+
const aab = yield* readArchiveBytes(inputs.archive);
|
|
7665
|
+
return yield* runGooglePlayPipeline({
|
|
7666
|
+
accessToken: token.accessToken,
|
|
7667
|
+
applicationId,
|
|
7668
|
+
aab,
|
|
7669
|
+
track: inputs.androidProfile.track ?? "internal",
|
|
7670
|
+
releaseStatus: inputs.androidProfile.releaseStatus ?? "completed",
|
|
7671
|
+
changesNotSentForReview: inputs.androidProfile.changesNotSentForReview ?? false,
|
|
7672
|
+
rollout: toDbNull(inputs.androidProfile.rollout)
|
|
7673
|
+
});
|
|
7674
|
+
}).pipe(Effect.catchTag("CliSubmitError", (engineError) => Effect.gen(function* () {
|
|
7675
|
+
yield* patchSubmissionStatus(inputs.api, inputs.submissionId, {
|
|
7676
|
+
status: "ERRORED",
|
|
7677
|
+
errorCode: engineError.code,
|
|
7678
|
+
errorMessage: engineError.message
|
|
7679
|
+
});
|
|
7680
|
+
return yield* Effect.fail(engineError);
|
|
7681
|
+
})));
|
|
7682
|
+
yield* patchSubmissionStatus(inputs.api, inputs.submissionId, { status: "FINISHED" });
|
|
7683
|
+
yield* printHuman(`Google Play bundle uploaded (versionCode ${String(result.versionCode)})`);
|
|
7684
|
+
return result;
|
|
7685
|
+
});
|
|
7686
|
+
|
|
6802
7687
|
//#endregion
|
|
6803
7688
|
//#region src/application/build-workflow.ts
|
|
7689
|
+
const buildAutoSubmitIosConfig = (iosProfile, whatToTest) => {
|
|
7690
|
+
if (iosProfile?.bundleIdentifier === void 0) return;
|
|
7691
|
+
return {
|
|
7692
|
+
bundleIdentifier: iosProfile.bundleIdentifier,
|
|
7693
|
+
...iosProfile.appleId === void 0 ? {} : { appleId: iosProfile.appleId },
|
|
7694
|
+
...iosProfile.ascAppId === void 0 ? {} : { ascAppId: iosProfile.ascAppId },
|
|
7695
|
+
...iosProfile.appleTeamId === void 0 ? {} : { appleTeamId: iosProfile.appleTeamId },
|
|
7696
|
+
...iosProfile.sku === void 0 ? {} : { sku: iosProfile.sku },
|
|
7697
|
+
...iosProfile.language === void 0 ? {} : { language: iosProfile.language },
|
|
7698
|
+
...iosProfile.companyName === void 0 ? {} : { companyName: iosProfile.companyName },
|
|
7699
|
+
...iosProfile.appName === void 0 ? {} : { appName: iosProfile.appName },
|
|
7700
|
+
...iosProfile.groups === void 0 ? {} : { groups: iosProfile.groups },
|
|
7701
|
+
...whatToTest === void 0 ? {} : { whatToTest }
|
|
7702
|
+
};
|
|
7703
|
+
};
|
|
7704
|
+
const buildAutoSubmitAndroidConfig = (androidProfile) => {
|
|
7705
|
+
if (androidProfile?.applicationId === void 0) return;
|
|
7706
|
+
return {
|
|
7707
|
+
applicationId: androidProfile.applicationId,
|
|
7708
|
+
...androidProfile.track === void 0 ? {} : { track: androidProfile.track },
|
|
7709
|
+
...androidProfile.releaseStatus === void 0 ? {} : { releaseStatus: androidProfile.releaseStatus },
|
|
7710
|
+
...androidProfile.changesNotSentForReview === void 0 ? {} : { changesNotSentForReview: androidProfile.changesNotSentForReview },
|
|
7711
|
+
...androidProfile.rollout === void 0 ? {} : { rollout: androidProfile.rollout }
|
|
7712
|
+
};
|
|
7713
|
+
};
|
|
7714
|
+
const runAutoSubmit = (input) => Effect.gen(function* () {
|
|
7715
|
+
yield* printHuman(`\nAuto-submitting build ${input.buildId} (profile ${input.profileName})...`);
|
|
7716
|
+
const easProfile = yield* resolveEasSubmitProfile((yield* readEasJson(process.cwd())).submit, input.profileName);
|
|
7717
|
+
const archiveUrl = (yield* input.api.builds.getInstallLink({ path: { id: input.buildId } })).artifactUrl;
|
|
7718
|
+
const iosConfig = input.platform === "ios" ? buildAutoSubmitIosConfig(easProfile.ios, input.whatToTest) : void 0;
|
|
7719
|
+
const androidConfig = input.platform === "android" ? buildAutoSubmitAndroidConfig(easProfile.android) : void 0;
|
|
7720
|
+
const submission = yield* createSubmissionViaApi(input.api, {
|
|
7721
|
+
projectId: input.projectId,
|
|
7722
|
+
platform: input.platform,
|
|
7723
|
+
profileName: input.profileName,
|
|
7724
|
+
archiveSource: "build",
|
|
7725
|
+
buildId: input.buildId,
|
|
7726
|
+
archiveUrl,
|
|
7727
|
+
...iosConfig === void 0 ? {} : { iosConfig },
|
|
7728
|
+
...androidConfig === void 0 ? {} : { androidConfig }
|
|
7729
|
+
});
|
|
7730
|
+
yield* printHuman(`Submission created: ${submission.id} (${submission.status})`);
|
|
7731
|
+
if (input.platform === "ios" && easProfile.ios?.ascApiKeyId !== void 0) {
|
|
7732
|
+
yield* printHuman("Running xcrun altool upload...");
|
|
7733
|
+
yield* runIosAltoolUpload({
|
|
7734
|
+
api: input.api,
|
|
7735
|
+
submissionId: submission.id,
|
|
7736
|
+
ipaPath: archiveUrl,
|
|
7737
|
+
ascApiKeyId: easProfile.ios.ascApiKeyId
|
|
7738
|
+
});
|
|
7739
|
+
}
|
|
7740
|
+
yield* printHuman(`Submission final status: ${(yield* pollSubmissionUntilTerminal(input.api, submission.id)).status}`);
|
|
7741
|
+
});
|
|
6804
7742
|
const runIosPlatformBuild = (input) => Effect.gen(function* () {
|
|
6805
7743
|
const { api, appMeta, envVars, options, profile, projectId, projectRoot, tempDir } = input;
|
|
6806
7744
|
if (!profile.ios) return yield* new BuildProfileError({ message: `Profile "${profile.name}" has no ios section.` });
|
|
@@ -6849,7 +7787,8 @@ const runAndroidPlatformBuild = (input) => Effect.gen(function* () {
|
|
|
6849
7787
|
yield* warnOnGradleMismatch(gradleConfig, androidBundleId);
|
|
6850
7788
|
const applicationIdentifier = gradleConfig?.applicationId ?? androidBundleId;
|
|
6851
7789
|
const credentialsSource = profile.credentialsSource ?? "remote";
|
|
6852
|
-
|
|
7790
|
+
const skipCredentials = profile.developmentClient === true || profile.withoutCredentials === true;
|
|
7791
|
+
if (credentialsSource === "remote" && !skipCredentials) yield* ensureAndroidCredentials(api, {
|
|
6853
7792
|
projectId,
|
|
6854
7793
|
applicationIdentifier
|
|
6855
7794
|
}, { freezeCredentials: options.freezeCredentials ?? false });
|
|
@@ -6863,7 +7802,8 @@ const runAndroidPlatformBuild = (input) => Effect.gen(function* () {
|
|
|
6863
7802
|
envVars,
|
|
6864
7803
|
projectId,
|
|
6865
7804
|
credentialsSource,
|
|
6866
|
-
profileName: profile.name
|
|
7805
|
+
profileName: profile.name,
|
|
7806
|
+
skipCredentials
|
|
6867
7807
|
}),
|
|
6868
7808
|
target: androidProfile.format === "aab" ? {
|
|
6869
7809
|
platform: "android",
|
|
@@ -6901,10 +7841,14 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
|
|
|
6901
7841
|
const projectId = yield* extractProjectId(baseConfig);
|
|
6902
7842
|
const platform = yield* detectPlatform(options.platform, baseConfig);
|
|
6903
7843
|
const profile = yield* readBuildProfile(userCwd, yield* resolveProfileName(userCwd, options.profileName));
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
|
|
6907
|
-
|
|
7844
|
+
if (profile.developmentClient === true) yield* warnIfDevClientMissing(userCwd);
|
|
7845
|
+
const envVars = {
|
|
7846
|
+
...yield* pullEnvVars(api, {
|
|
7847
|
+
projectId,
|
|
7848
|
+
environment: profile.environment
|
|
7849
|
+
}),
|
|
7850
|
+
...profile.env
|
|
7851
|
+
};
|
|
6908
7852
|
yield* applyAutoIncrement({
|
|
6909
7853
|
projectRoot: userCwd,
|
|
6910
7854
|
platform,
|
|
@@ -6991,6 +7935,14 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
|
|
|
6991
7935
|
["SHA-256", build.sha256],
|
|
6992
7936
|
["Bytes", String(build.byteSize)]
|
|
6993
7937
|
]);
|
|
7938
|
+
if (options.autoSubmit === true) yield* runAutoSubmit({
|
|
7939
|
+
api,
|
|
7940
|
+
buildId: result.id,
|
|
7941
|
+
projectId,
|
|
7942
|
+
platform,
|
|
7943
|
+
profileName: options.autoSubmitProfile ?? profile.name,
|
|
7944
|
+
...options.whatToTest === void 0 ? {} : { whatToTest: options.whatToTest }
|
|
7945
|
+
});
|
|
6994
7946
|
}));
|
|
6995
7947
|
|
|
6996
7948
|
//#endregion
|
|
@@ -7153,6 +8105,19 @@ const buildCommand = defineCommand({
|
|
|
7153
8105
|
"allow-dirty": {
|
|
7154
8106
|
type: "boolean",
|
|
7155
8107
|
description: "Proceed even with uncommitted git changes"
|
|
8108
|
+
},
|
|
8109
|
+
"auto-submit": {
|
|
8110
|
+
type: "boolean",
|
|
8111
|
+
alias: "s",
|
|
8112
|
+
description: "After upload, submit the build using eas.json submit profile of the same name"
|
|
8113
|
+
},
|
|
8114
|
+
"auto-submit-with-profile": {
|
|
8115
|
+
type: "string",
|
|
8116
|
+
description: "After upload, submit the build using a specific submit profile"
|
|
8117
|
+
},
|
|
8118
|
+
"what-to-test": {
|
|
8119
|
+
type: "string",
|
|
8120
|
+
description: "iOS-only TestFlight changelog when auto-submitting"
|
|
7156
8121
|
}
|
|
7157
8122
|
},
|
|
7158
8123
|
run: async ({ args }) => runEffect(runBuildWorkflow({
|
|
@@ -7164,7 +8129,13 @@ const buildCommand = defineCommand({
|
|
|
7164
8129
|
rawOutput: args["raw-output"] ?? false,
|
|
7165
8130
|
clearCache: args["clear-cache"] ?? false,
|
|
7166
8131
|
freezeCredentials: args["freeze-credentials"] ?? false,
|
|
7167
|
-
allowDirty: args["allow-dirty"] ?? false
|
|
8132
|
+
allowDirty: args["allow-dirty"] ?? false,
|
|
8133
|
+
...args["auto-submit"] === void 0 ? {} : { autoSubmit: args["auto-submit"] },
|
|
8134
|
+
...args["auto-submit-with-profile"] === void 0 ? {} : {
|
|
8135
|
+
autoSubmit: true,
|
|
8136
|
+
autoSubmitProfile: args["auto-submit-with-profile"]
|
|
8137
|
+
},
|
|
8138
|
+
...args["what-to-test"] === void 0 ? {} : { whatToTest: args["what-to-test"] }
|
|
7168
8139
|
}), BUILD_EXIT_EXTRAS)
|
|
7169
8140
|
});
|
|
7170
8141
|
|
|
@@ -7431,7 +8402,18 @@ const listCommand$5 = defineCommand({
|
|
|
7431
8402
|
|
|
7432
8403
|
//#endregion
|
|
7433
8404
|
//#region src/commands/builds/resign.ts
|
|
7434
|
-
const resignWorkflowText = (
|
|
8405
|
+
const resignWorkflowText = (params) => {
|
|
8406
|
+
const inputs = params.resolved;
|
|
8407
|
+
const profilePathHint = inputs === void 0 ? "/path/to/new.mobileprovision" : inputs.profilePath;
|
|
8408
|
+
const identityHint = inputs === void 0 ? `"iPhone Distribution: Your Team (ABCDE12345)"` : `"${inputs.signingIdentity}"`;
|
|
8409
|
+
const appName = inputs === void 0 ? "YourApp.app" : `${inputs.appName}.app`;
|
|
8410
|
+
const resolvedHeader = inputs === void 0 ? "" : `Resolved inputs
|
|
8411
|
+
Profile: ${inputs.profilePath}
|
|
8412
|
+
Identity: ${inputs.signingIdentity}
|
|
8413
|
+
Target app bundle: ${inputs.appName}.app
|
|
8414
|
+
|
|
8415
|
+
`;
|
|
8416
|
+
return `Resigning iOS build ${params.buildId}
|
|
7435
8417
|
=================================================
|
|
7436
8418
|
|
|
7437
8419
|
iOS code-signing requires native macOS tooling (codesign, security, xcodebuild)
|
|
@@ -7439,22 +8421,22 @@ and the matching distribution certificate in your Keychain. better-update does
|
|
|
7439
8421
|
not bundle that toolchain — instead it gives you the inputs and a re-upload
|
|
7440
8422
|
path.
|
|
7441
8423
|
|
|
7442
|
-
Step 1 — Download the existing IPA
|
|
7443
|
-
${installLink}
|
|
8424
|
+
${resolvedHeader}Step 1 — Download the existing IPA
|
|
8425
|
+
${params.installLink}
|
|
7444
8426
|
|
|
7445
8427
|
Step 2 — Resign the IPA locally with your new provisioning profile.
|
|
7446
8428
|
Pick one of:
|
|
7447
8429
|
a) fastlane sigh resign:
|
|
7448
8430
|
fastlane sigh resign /tmp/build.ipa \\
|
|
7449
|
-
--signing_identity
|
|
7450
|
-
--provisioning_profile
|
|
8431
|
+
--signing_identity ${identityHint} \\
|
|
8432
|
+
--provisioning_profile ${profilePathHint}
|
|
7451
8433
|
|
|
7452
8434
|
b) Apple's codesign + xcodebuild:
|
|
7453
8435
|
unzip /tmp/build.ipa -d /tmp/payload
|
|
7454
|
-
cp
|
|
7455
|
-
codesign -f -s
|
|
7456
|
-
--entitlements <(security cms -D -i
|
|
7457
|
-
/tmp/payload/Payload
|
|
8436
|
+
cp ${profilePathHint} /tmp/payload/Payload/${appName}/embedded.mobileprovision
|
|
8437
|
+
codesign -f -s ${identityHint} \\
|
|
8438
|
+
--entitlements <(security cms -D -i ${profilePathHint}) \\
|
|
8439
|
+
/tmp/payload/Payload/${appName}
|
|
7458
8440
|
(cd /tmp/payload && zip -qr /tmp/resigned.ipa Payload)
|
|
7459
8441
|
|
|
7460
8442
|
Step 3 — Upload the re-signed IPA as a fresh build:
|
|
@@ -7464,16 +8446,40 @@ Step 3 — Upload the re-signed IPA as a fresh build:
|
|
|
7464
8446
|
The new build will get a fresh build ID. The original build remains for
|
|
7465
8447
|
rollback. Disable or delete it when the re-signed build is verified.
|
|
7466
8448
|
`;
|
|
8449
|
+
};
|
|
8450
|
+
const downloadProfileToTmp = (api, profileId) => Effect.gen(function* () {
|
|
8451
|
+
const data = yield* api.appleProvisioningProfiles.download({ path: { id: profileId } });
|
|
8452
|
+
const target = path.join(tmpdir(), `better-update-resign-${data.id}.mobileprovision`);
|
|
8453
|
+
yield* Effect.promise(async () => writeFile(target, fromBase64(data.profileBase64)));
|
|
8454
|
+
return {
|
|
8455
|
+
profilePath: target,
|
|
8456
|
+
profileName: data.profileName,
|
|
8457
|
+
bundleIdentifier: data.bundleIdentifier
|
|
8458
|
+
};
|
|
8459
|
+
});
|
|
8460
|
+
const resolveSigningIdentity = (api, certId) => Effect.gen(function* () {
|
|
8461
|
+
return `iPhone Distribution: ${(yield* api.appleDistributionCertificates.download({ path: { id: certId } })).appleTeamIdentifier}`;
|
|
8462
|
+
});
|
|
7467
8463
|
const resignCommand = defineCommand({
|
|
7468
8464
|
meta: {
|
|
7469
8465
|
name: "resign",
|
|
7470
8466
|
description: "Print step-by-step instructions for re-signing an iOS build with a new provisioning profile"
|
|
7471
8467
|
},
|
|
7472
|
-
args: {
|
|
7473
|
-
|
|
7474
|
-
|
|
7475
|
-
|
|
7476
|
-
|
|
8468
|
+
args: {
|
|
8469
|
+
build: {
|
|
8470
|
+
type: "string",
|
|
8471
|
+
required: true,
|
|
8472
|
+
description: "Source build ID"
|
|
8473
|
+
},
|
|
8474
|
+
"profile-id": {
|
|
8475
|
+
type: "string",
|
|
8476
|
+
description: "Provisioning profile ID to bind to the resigned build (downloads it to a tmp path)"
|
|
8477
|
+
},
|
|
8478
|
+
"cert-id": {
|
|
8479
|
+
type: "string",
|
|
8480
|
+
description: "Distribution certificate ID to derive the codesign --signing-identity from"
|
|
8481
|
+
}
|
|
8482
|
+
},
|
|
7477
8483
|
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
7478
8484
|
const api = yield* apiClient;
|
|
7479
8485
|
const build = yield* api.builds.get({ path: { id: args.build } });
|
|
@@ -7483,7 +8489,19 @@ const resignCommand = defineCommand({
|
|
|
7483
8489
|
return;
|
|
7484
8490
|
}
|
|
7485
8491
|
const link = yield* api.builds.getInstallLink({ path: { id: args.build } });
|
|
7486
|
-
|
|
8492
|
+
const profilePromise = args["profile-id"] === void 0 ? Effect.succeed(void 0) : downloadProfileToTmp(api, args["profile-id"]);
|
|
8493
|
+
const identityPromise = args["cert-id"] === void 0 ? Effect.succeed(void 0) : resolveSigningIdentity(api, args["cert-id"]);
|
|
8494
|
+
const [profile, identity] = yield* Effect.all([profilePromise, identityPromise], { concurrency: 2 });
|
|
8495
|
+
const resolved = profile === void 0 && identity === void 0 ? void 0 : {
|
|
8496
|
+
profilePath: profile?.profilePath ?? "/path/to/new.mobileprovision",
|
|
8497
|
+
signingIdentity: identity ?? "iPhone Distribution: Your Team (ABCDE12345)",
|
|
8498
|
+
appName: profile?.bundleIdentifier.split(".").pop() ?? "YourApp"
|
|
8499
|
+
};
|
|
8500
|
+
yield* printHuman(resignWorkflowText({
|
|
8501
|
+
buildId: args.build,
|
|
8502
|
+
installLink: link.artifactUrl,
|
|
8503
|
+
resolved
|
|
8504
|
+
}));
|
|
7487
8505
|
}))
|
|
7488
8506
|
});
|
|
7489
8507
|
|
|
@@ -7845,10 +8863,13 @@ const runUploadWorkflow = (options) => Effect.gen(function* () {
|
|
|
7845
8863
|
if (!(yield* (yield* FileSystem.FileSystem).exists(options.artifactPath).pipe(Effect.orElseSucceed(() => false)))) yield* new ArtifactNotFoundError({ message: `Artifact not found at ${options.artifactPath}.` });
|
|
7846
8864
|
const projectId = yield* extractProjectId(yield* readExpoConfig(projectRoot));
|
|
7847
8865
|
const profile = yield* readBuildProfile(projectRoot, options.profileName);
|
|
7848
|
-
const appMeta = yield* readAppMeta(yield* readExpoConfig(projectRoot,
|
|
7849
|
-
|
|
7850
|
-
|
|
7851
|
-
|
|
8866
|
+
const appMeta = yield* readAppMeta(yield* readExpoConfig(projectRoot, {
|
|
8867
|
+
...yield* pullEnvVars(api, {
|
|
8868
|
+
projectId,
|
|
8869
|
+
environment: profile.environment
|
|
8870
|
+
}),
|
|
8871
|
+
...profile.env
|
|
8872
|
+
}), options.platform);
|
|
7852
8873
|
const runtimeVersion = yield* resolveRuntimeVersion({
|
|
7853
8874
|
raw: appMeta.rawRuntimeVersion,
|
|
7854
8875
|
appVersion: appMeta.appVersion,
|
|
@@ -12957,6 +13978,195 @@ const statusCommand = defineCommand({
|
|
|
12957
13978
|
}))
|
|
12958
13979
|
});
|
|
12959
13980
|
|
|
13981
|
+
//#endregion
|
|
13982
|
+
//#region src/commands/submit/index.ts
|
|
13983
|
+
const PLATFORMS = ["ios", "android"];
|
|
13984
|
+
const resolveArchive = (api, projectId, platform, args) => Effect.gen(function* () {
|
|
13985
|
+
if (args.path !== void 0) return {
|
|
13986
|
+
archiveSource: "path",
|
|
13987
|
+
archiveUrl: args.path,
|
|
13988
|
+
buildId: void 0
|
|
13989
|
+
};
|
|
13990
|
+
if (args.url !== void 0) return {
|
|
13991
|
+
archiveSource: "url",
|
|
13992
|
+
archiveUrl: args.url,
|
|
13993
|
+
buildId: void 0
|
|
13994
|
+
};
|
|
13995
|
+
if (args.id !== void 0) return {
|
|
13996
|
+
archiveSource: "build",
|
|
13997
|
+
archiveUrl: (yield* api.builds.getInstallLink({ path: { id: args.id } })).artifactUrl,
|
|
13998
|
+
buildId: args.id
|
|
13999
|
+
};
|
|
14000
|
+
if (args.latest) {
|
|
14001
|
+
const { items } = yield* api.builds.list({ urlParams: {
|
|
14002
|
+
projectId,
|
|
14003
|
+
limit: 1,
|
|
14004
|
+
platform,
|
|
14005
|
+
sort: "-createdAt"
|
|
14006
|
+
} });
|
|
14007
|
+
const [latest] = items;
|
|
14008
|
+
if (latest === void 0) {
|
|
14009
|
+
yield* printHuman(`No builds found for platform ${platform}`);
|
|
14010
|
+
return null;
|
|
14011
|
+
}
|
|
14012
|
+
return {
|
|
14013
|
+
archiveSource: "build",
|
|
14014
|
+
archiveUrl: (yield* api.builds.getInstallLink({ path: { id: latest.id } })).artifactUrl,
|
|
14015
|
+
buildId: latest.id
|
|
14016
|
+
};
|
|
14017
|
+
}
|
|
14018
|
+
return null;
|
|
14019
|
+
});
|
|
14020
|
+
const buildIosCreatePayload = (iosProfile, whatToTest) => {
|
|
14021
|
+
if (iosProfile?.bundleIdentifier === void 0) return;
|
|
14022
|
+
return {
|
|
14023
|
+
bundleIdentifier: iosProfile.bundleIdentifier,
|
|
14024
|
+
...iosProfile.appleId === void 0 ? {} : { appleId: iosProfile.appleId },
|
|
14025
|
+
...iosProfile.ascAppId === void 0 ? {} : { ascAppId: iosProfile.ascAppId },
|
|
14026
|
+
...iosProfile.appleTeamId === void 0 ? {} : { appleTeamId: iosProfile.appleTeamId },
|
|
14027
|
+
...iosProfile.sku === void 0 ? {} : { sku: iosProfile.sku },
|
|
14028
|
+
...iosProfile.language === void 0 ? {} : { language: iosProfile.language },
|
|
14029
|
+
...iosProfile.companyName === void 0 ? {} : { companyName: iosProfile.companyName },
|
|
14030
|
+
...iosProfile.appName === void 0 ? {} : { appName: iosProfile.appName },
|
|
14031
|
+
...iosProfile.groups === void 0 ? {} : { groups: iosProfile.groups },
|
|
14032
|
+
...whatToTest === void 0 ? {} : { whatToTest }
|
|
14033
|
+
};
|
|
14034
|
+
};
|
|
14035
|
+
const buildAndroidCreatePayload = (androidProfile) => {
|
|
14036
|
+
if (androidProfile?.applicationId === void 0) return;
|
|
14037
|
+
return {
|
|
14038
|
+
applicationId: androidProfile.applicationId,
|
|
14039
|
+
...androidProfile.track === void 0 ? {} : { track: androidProfile.track },
|
|
14040
|
+
...androidProfile.releaseStatus === void 0 ? {} : { releaseStatus: androidProfile.releaseStatus },
|
|
14041
|
+
...androidProfile.changesNotSentForReview === void 0 ? {} : { changesNotSentForReview: androidProfile.changesNotSentForReview },
|
|
14042
|
+
...androidProfile.rollout === void 0 ? {} : { rollout: androidProfile.rollout }
|
|
14043
|
+
};
|
|
14044
|
+
};
|
|
14045
|
+
const runFlow = (api, projectId, args) => Effect.gen(function* () {
|
|
14046
|
+
const iosConfig = buildIosCreatePayload(args.easProfile.ios, args.whatToTest);
|
|
14047
|
+
const androidConfig = buildAndroidCreatePayload(args.easProfile.android);
|
|
14048
|
+
const submission = yield* createSubmissionViaApi(api, {
|
|
14049
|
+
projectId,
|
|
14050
|
+
platform: args.platform,
|
|
14051
|
+
profileName: args.profile,
|
|
14052
|
+
archiveSource: args.archive.archiveSource,
|
|
14053
|
+
buildId: args.archive.buildId,
|
|
14054
|
+
archiveUrl: args.archive.archiveUrl,
|
|
14055
|
+
...iosConfig === void 0 ? {} : { iosConfig },
|
|
14056
|
+
...androidConfig === void 0 ? {} : { androidConfig }
|
|
14057
|
+
});
|
|
14058
|
+
yield* printHuman(`Submission created: ${submission.id} (${submission.status})`);
|
|
14059
|
+
if (args.platform === "ios" && iosConfig !== void 0) {
|
|
14060
|
+
const ascApiKeyId = args.easProfile.ios?.ascApiKeyId;
|
|
14061
|
+
if (ascApiKeyId === void 0) {
|
|
14062
|
+
yield* printHuman("iOS submission queued. Resolve ascApiKeyId in eas.json submit profile to enable client-side altool upload.");
|
|
14063
|
+
return submission;
|
|
14064
|
+
}
|
|
14065
|
+
yield* printHuman("Running xcrun altool upload locally...");
|
|
14066
|
+
yield* runIosAltoolUpload({
|
|
14067
|
+
api,
|
|
14068
|
+
submissionId: submission.id,
|
|
14069
|
+
ipaPath: args.archive.archiveUrl,
|
|
14070
|
+
ascApiKeyId
|
|
14071
|
+
});
|
|
14072
|
+
}
|
|
14073
|
+
if (args.platform === "android" && args.easProfile.android !== void 0) {
|
|
14074
|
+
yield* printHuman("Uploading bundle to Google Play locally...");
|
|
14075
|
+
const serviceAccountKeyId = args.serviceAccountKeyId ?? args.easProfile.android.serviceAccountKeyId;
|
|
14076
|
+
yield* runAndroidGooglePlayUpload({
|
|
14077
|
+
api,
|
|
14078
|
+
submissionId: submission.id,
|
|
14079
|
+
archive: {
|
|
14080
|
+
source: args.archive.archiveSource,
|
|
14081
|
+
value: args.archive.archiveUrl
|
|
14082
|
+
},
|
|
14083
|
+
androidProfile: args.easProfile.android,
|
|
14084
|
+
serviceAccountKeyId
|
|
14085
|
+
});
|
|
14086
|
+
}
|
|
14087
|
+
if (!args.wait) return submission;
|
|
14088
|
+
const terminal = yield* pollSubmissionUntilTerminal(api, submission.id);
|
|
14089
|
+
yield* printHuman(`Final status: ${terminal.status}`);
|
|
14090
|
+
if (terminal.errorCode !== null) yield* printHuman(`Error ${terminal.errorCode}: ${terminal.errorMessage ?? "(no detail)"}`);
|
|
14091
|
+
return terminal;
|
|
14092
|
+
});
|
|
14093
|
+
const submitCommand = defineCommand({
|
|
14094
|
+
meta: {
|
|
14095
|
+
name: "submit",
|
|
14096
|
+
description: "Submit a build to App Store Connect or Google Play"
|
|
14097
|
+
},
|
|
14098
|
+
args: {
|
|
14099
|
+
platform: {
|
|
14100
|
+
type: "enum",
|
|
14101
|
+
options: [...PLATFORMS],
|
|
14102
|
+
description: "Target platform"
|
|
14103
|
+
},
|
|
14104
|
+
profile: {
|
|
14105
|
+
type: "string",
|
|
14106
|
+
default: "production",
|
|
14107
|
+
description: "eas.json submit profile name (default: production)"
|
|
14108
|
+
},
|
|
14109
|
+
latest: {
|
|
14110
|
+
type: "boolean",
|
|
14111
|
+
description: "Submit the latest build for the platform"
|
|
14112
|
+
},
|
|
14113
|
+
id: {
|
|
14114
|
+
type: "string",
|
|
14115
|
+
description: "Submit a specific build by ID"
|
|
14116
|
+
},
|
|
14117
|
+
path: {
|
|
14118
|
+
type: "string",
|
|
14119
|
+
description: "Submit a local IPA/AAB at this path (URL or file://)"
|
|
14120
|
+
},
|
|
14121
|
+
url: {
|
|
14122
|
+
type: "string",
|
|
14123
|
+
description: "Submit a binary fetched from this URL"
|
|
14124
|
+
},
|
|
14125
|
+
"what-to-test": {
|
|
14126
|
+
type: "string",
|
|
14127
|
+
description: "iOS-only TestFlight changelog ('What to test')"
|
|
14128
|
+
},
|
|
14129
|
+
"service-account-key-id": {
|
|
14130
|
+
type: "string",
|
|
14131
|
+
description: "Android-only: better-update saved Google service account key ID (overrides eas.json submit profile)"
|
|
14132
|
+
},
|
|
14133
|
+
wait: {
|
|
14134
|
+
type: "boolean",
|
|
14135
|
+
default: true,
|
|
14136
|
+
description: "Block until submission reaches a terminal status (default: true)"
|
|
14137
|
+
}
|
|
14138
|
+
},
|
|
14139
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
14140
|
+
const { platform } = args;
|
|
14141
|
+
if (platform === void 0) {
|
|
14142
|
+
yield* printHuman("--platform is required (ios | android)");
|
|
14143
|
+
return;
|
|
14144
|
+
}
|
|
14145
|
+
const projectId = yield* readProjectId;
|
|
14146
|
+
const api = yield* apiClient;
|
|
14147
|
+
const easProfile = yield* resolveEasSubmitProfile((yield* readEasJson(process.cwd())).submit, args.profile);
|
|
14148
|
+
const archive = yield* resolveArchive(api, projectId, platform, {
|
|
14149
|
+
id: args.id,
|
|
14150
|
+
path: args.path,
|
|
14151
|
+
url: args.url,
|
|
14152
|
+
latest: args.latest ?? false
|
|
14153
|
+
});
|
|
14154
|
+
if (archive === null) {
|
|
14155
|
+
yield* printHuman("No archive resolved. Pass one of --latest, --id, --path, or --url.");
|
|
14156
|
+
return;
|
|
14157
|
+
}
|
|
14158
|
+
yield* runFlow(api, projectId, {
|
|
14159
|
+
platform,
|
|
14160
|
+
profile: args.profile,
|
|
14161
|
+
easProfile,
|
|
14162
|
+
archive,
|
|
14163
|
+
...args["what-to-test"] === void 0 ? {} : { whatToTest: args["what-to-test"] },
|
|
14164
|
+
...args["service-account-key-id"] === void 0 ? {} : { serviceAccountKeyId: args["service-account-key-id"] },
|
|
14165
|
+
wait: args.wait
|
|
14166
|
+
});
|
|
14167
|
+
}))
|
|
14168
|
+
});
|
|
14169
|
+
|
|
12960
14170
|
//#endregion
|
|
12961
14171
|
//#region src/commands/update/configure.ts
|
|
12962
14172
|
const RUNTIME_POLICIES = ["appVersion", "fingerprint"];
|
|
@@ -15042,7 +16252,8 @@ await runMain(defineCommand({
|
|
|
15042
16252
|
webhooks: webhooksCommand,
|
|
15043
16253
|
autocomplete: autocompleteCommand,
|
|
15044
16254
|
"migrate-config": migrateConfigCommand,
|
|
15045
|
-
apple: appleCommand
|
|
16255
|
+
apple: appleCommand,
|
|
16256
|
+
submit: submitCommand
|
|
15046
16257
|
}
|
|
15047
16258
|
}));
|
|
15048
16259
|
|