@better-update/cli 0.36.2 → 0.38.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 +883 -136
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -35,7 +35,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
35
35
|
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region package.json
|
|
38
|
-
var version = "0.
|
|
38
|
+
var version = "0.38.0";
|
|
39
39
|
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region src/lib/interactive-mode.ts
|
|
@@ -425,11 +425,11 @@ const Ciphertext = Schema.String.pipe(Schema.minLength(1)).annotations({ descrip
|
|
|
425
425
|
const WrappedDek = Schema.String.pipe(Schema.minLength(1)).annotations({ description: "Base64 of the DEK wrapped under the org vault key" });
|
|
426
426
|
/**
|
|
427
427
|
* The secret kinds whose DEK is wrapped under the org vault key — the rows a
|
|
428
|
-
* rotation must re-wrap.
|
|
428
|
+
* rotation must re-wrap. Eight signing-credential tables plus `envVarValue` (one
|
|
429
429
|
* row per environment variable value revision). Provisioning profiles are
|
|
430
430
|
* plaintext and are deliberately absent.
|
|
431
431
|
*/
|
|
432
|
-
const CredentialType = Schema.Literal("appleDistributionCertificate", "applePushKey", "ascApiKey", "googleServiceAccountKey", "androidUploadKeystore", "envVarValue").annotations({ description: "Which encrypted-secret table a vault-key DEK re-wrap targets" });
|
|
432
|
+
const CredentialType = Schema.Literal("appleDistributionCertificate", "applePushKey", "applePushCertificate", "applePayCertificate", "applePassTypeCertificate", "ascApiKey", "googleServiceAccountKey", "androidUploadKeystore", "envVarValue").annotations({ description: "Which encrypted-secret table a vault-key DEK re-wrap targets" });
|
|
433
433
|
/**
|
|
434
434
|
* The client-encrypted envelope. Spread into each secret credential's upload
|
|
435
435
|
* body and download result alongside that credential's public metadata. The
|
|
@@ -665,6 +665,128 @@ var AppleDistributionCertificatesGroup = class extends HttpApiGroup.make("appleD
|
|
|
665
665
|
description: "Manage .p12 distribution certificates"
|
|
666
666
|
})) {};
|
|
667
667
|
|
|
668
|
+
//#endregion
|
|
669
|
+
//#region ../../packages/api/src/domain/apple-pass-type-certificate.ts
|
|
670
|
+
/**
|
|
671
|
+
* Pass Type ID certificate (Wallet passes), bound to a Pass Type ID
|
|
672
|
+
* (`pass.*`). The library has no Pass Type ID API, so these are uploaded
|
|
673
|
+
* manually: the CLI seals the `.p12` (cert + key) and the server stores only the
|
|
674
|
+
* envelope + metadata.
|
|
675
|
+
*/
|
|
676
|
+
var ApplePassTypeCertificate = class extends Schema.Class("ApplePassTypeCertificate")({
|
|
677
|
+
id: Id,
|
|
678
|
+
organizationId: Id,
|
|
679
|
+
appleTeamId: Id,
|
|
680
|
+
passTypeIdentifier: Schema.String,
|
|
681
|
+
serialNumber: Schema.String,
|
|
682
|
+
validFrom: DateTimeString,
|
|
683
|
+
validUntil: DateTimeString,
|
|
684
|
+
createdAt: DateTimeString,
|
|
685
|
+
updatedAt: DateTimeString
|
|
686
|
+
}) {};
|
|
687
|
+
/** Client-encrypted upload: the `.p12` bytes + password are sealed into `ciphertext`. */
|
|
688
|
+
const UploadApplePassTypeCertificateBody = Schema.Struct({
|
|
689
|
+
id: Id,
|
|
690
|
+
...encryptedEnvelopeFields,
|
|
691
|
+
passTypeIdentifier: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(200)),
|
|
692
|
+
serialNumber: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(200)),
|
|
693
|
+
appleTeamIdentifier: AppleTeamIdentifier,
|
|
694
|
+
...appleTeamMetadataFields,
|
|
695
|
+
validFrom: DateTimeString,
|
|
696
|
+
validUntil: DateTimeString
|
|
697
|
+
});
|
|
698
|
+
const DeleteApplePassTypeCertificateResult = DeletedResult;
|
|
699
|
+
/** The encrypted envelope (relayed from R2) plus metadata; the CLI decrypts `ciphertext` to recover `{ p12Base64, p12Password }`. */
|
|
700
|
+
const DownloadApplePassTypeCertificateResult = Schema.Struct({
|
|
701
|
+
id: Id,
|
|
702
|
+
...encryptedEnvelopeFields,
|
|
703
|
+
passTypeIdentifier: Schema.String,
|
|
704
|
+
serialNumber: Schema.String,
|
|
705
|
+
appleTeamIdentifier: AppleTeamIdentifier,
|
|
706
|
+
validFrom: DateTimeString,
|
|
707
|
+
validUntil: DateTimeString
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
//#endregion
|
|
711
|
+
//#region ../../packages/api/src/groups/apple-pass-type-certificates.ts
|
|
712
|
+
var ApplePassTypeCertificatesGroup = class extends HttpApiGroup.make("applePassTypeCertificates").add(HttpApiEndpoint.get("list", "/api/apple/pass-type-certificates").addSuccess(Schema.Struct({ items: Schema.Array(ApplePassTypeCertificate) })).annotateContext(OpenApi.annotations({
|
|
713
|
+
title: "List Apple Pass Type ID certificates",
|
|
714
|
+
description: "List Pass Type ID certificates for the organization"
|
|
715
|
+
}))).add(HttpApiEndpoint.post("upload", "/api/apple/pass-type-certificates").setPayload(UploadApplePassTypeCertificateBody).addSuccess(ApplePassTypeCertificate, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
716
|
+
title: "Upload Pass Type ID certificate",
|
|
717
|
+
description: "Upload a Wallet Pass Type ID .p12 certificate"
|
|
718
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/apple/pass-type-certificates/${idParam}`.addSuccess(DeleteApplePassTypeCertificateResult).annotateContext(OpenApi.annotations({
|
|
719
|
+
title: "Delete Pass Type ID certificate",
|
|
720
|
+
description: "Remove a stored Pass Type ID certificate"
|
|
721
|
+
}))).add(HttpApiEndpoint.get("download")`/api/apple/pass-type-certificates/${idParam}/download`.addSuccess(DownloadApplePassTypeCertificateResult).annotateContext(OpenApi.annotations({
|
|
722
|
+
title: "Download Pass Type ID certificate",
|
|
723
|
+
description: "Fetch the decrypted .p12 Pass Type ID certificate for local use (audit-logged)"
|
|
724
|
+
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
725
|
+
title: "Apple Pass Type ID Certificates",
|
|
726
|
+
description: "Manage Wallet Pass Type ID certificates"
|
|
727
|
+
})) {};
|
|
728
|
+
|
|
729
|
+
//#endregion
|
|
730
|
+
//#region ../../packages/api/src/domain/apple-pay-certificate.ts
|
|
731
|
+
/**
|
|
732
|
+
* Apple Pay Payment Processing certificate, bound to a Merchant ID
|
|
733
|
+
* (`merchant.*`). The library cannot create these (no portal cert type), so they
|
|
734
|
+
* are uploaded manually: the CLI seals the `.p12` (cert + key) and the server
|
|
735
|
+
* stores only the envelope + metadata.
|
|
736
|
+
*/
|
|
737
|
+
var ApplePayCertificate = class extends Schema.Class("ApplePayCertificate")({
|
|
738
|
+
id: Id,
|
|
739
|
+
organizationId: Id,
|
|
740
|
+
appleTeamId: Id,
|
|
741
|
+
merchantIdentifier: Schema.String,
|
|
742
|
+
serialNumber: Schema.String,
|
|
743
|
+
validFrom: DateTimeString,
|
|
744
|
+
validUntil: DateTimeString,
|
|
745
|
+
createdAt: DateTimeString,
|
|
746
|
+
updatedAt: DateTimeString
|
|
747
|
+
}) {};
|
|
748
|
+
/** Client-encrypted upload: the `.p12` bytes + password are sealed into `ciphertext`. */
|
|
749
|
+
const UploadApplePayCertificateBody = Schema.Struct({
|
|
750
|
+
id: Id,
|
|
751
|
+
...encryptedEnvelopeFields,
|
|
752
|
+
merchantIdentifier: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(200)),
|
|
753
|
+
serialNumber: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(200)),
|
|
754
|
+
appleTeamIdentifier: AppleTeamIdentifier,
|
|
755
|
+
...appleTeamMetadataFields,
|
|
756
|
+
validFrom: DateTimeString,
|
|
757
|
+
validUntil: DateTimeString
|
|
758
|
+
});
|
|
759
|
+
const DeleteApplePayCertificateResult = DeletedResult;
|
|
760
|
+
/** The encrypted envelope (relayed from R2) plus metadata; the CLI decrypts `ciphertext` to recover `{ p12Base64, p12Password }`. */
|
|
761
|
+
const DownloadApplePayCertificateResult = Schema.Struct({
|
|
762
|
+
id: Id,
|
|
763
|
+
...encryptedEnvelopeFields,
|
|
764
|
+
merchantIdentifier: Schema.String,
|
|
765
|
+
serialNumber: Schema.String,
|
|
766
|
+
appleTeamIdentifier: AppleTeamIdentifier,
|
|
767
|
+
validFrom: DateTimeString,
|
|
768
|
+
validUntil: DateTimeString
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
//#endregion
|
|
772
|
+
//#region ../../packages/api/src/groups/apple-pay-certificates.ts
|
|
773
|
+
var ApplePayCertificatesGroup = class extends HttpApiGroup.make("applePayCertificates").add(HttpApiEndpoint.get("list", "/api/apple/pay-certificates").addSuccess(Schema.Struct({ items: Schema.Array(ApplePayCertificate) })).annotateContext(OpenApi.annotations({
|
|
774
|
+
title: "List Apple Pay certificates",
|
|
775
|
+
description: "List Apple Pay payment processing certificates for the organization"
|
|
776
|
+
}))).add(HttpApiEndpoint.post("upload", "/api/apple/pay-certificates").setPayload(UploadApplePayCertificateBody).addSuccess(ApplePayCertificate, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
777
|
+
title: "Upload Apple Pay certificate",
|
|
778
|
+
description: "Upload an Apple Pay payment processing .p12 certificate"
|
|
779
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/apple/pay-certificates/${idParam}`.addSuccess(DeleteApplePayCertificateResult).annotateContext(OpenApi.annotations({
|
|
780
|
+
title: "Delete Apple Pay certificate",
|
|
781
|
+
description: "Remove a stored Apple Pay payment processing certificate"
|
|
782
|
+
}))).add(HttpApiEndpoint.get("download")`/api/apple/pay-certificates/${idParam}/download`.addSuccess(DownloadApplePayCertificateResult).annotateContext(OpenApi.annotations({
|
|
783
|
+
title: "Download Apple Pay certificate",
|
|
784
|
+
description: "Fetch the decrypted .p12 Apple Pay certificate for local use (audit-logged)"
|
|
785
|
+
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
786
|
+
title: "Apple Pay Certificates",
|
|
787
|
+
description: "Manage Apple Pay payment processing certificates"
|
|
788
|
+
})) {};
|
|
789
|
+
|
|
668
790
|
//#endregion
|
|
669
791
|
//#region ../../packages/api/src/domain/apple-provisioning-profile.ts
|
|
670
792
|
const DistributionType = Schema.Literal("APP_STORE", "AD_HOC", "ENTERPRISE", "DEVELOPMENT");
|
|
@@ -722,6 +844,71 @@ var AppleProvisioningProfilesGroup = class extends HttpApiGroup.make("appleProvi
|
|
|
722
844
|
description: "Manage .mobileprovision profiles (upload or generate)"
|
|
723
845
|
})) {};
|
|
724
846
|
|
|
847
|
+
//#endregion
|
|
848
|
+
//#region ../../packages/api/src/domain/apple-push-certificate.ts
|
|
849
|
+
/**
|
|
850
|
+
* Legacy APNs Push Services SSL certificate (the `.cer`/`.p12` push cert, distinct
|
|
851
|
+
* from the modern `.p8` token key in `apple-push-key.ts`). Bound to a single App
|
|
852
|
+
* ID (`bundleIdentifier`) rather than a whole team. The production cert serves
|
|
853
|
+
* both the sandbox and production APNs environments.
|
|
854
|
+
*/
|
|
855
|
+
var ApplePushCertificate = class extends Schema.Class("ApplePushCertificate")({
|
|
856
|
+
id: Id,
|
|
857
|
+
organizationId: Id,
|
|
858
|
+
appleTeamId: Id,
|
|
859
|
+
bundleIdentifier: Schema.String,
|
|
860
|
+
serialNumber: Schema.String,
|
|
861
|
+
validFrom: DateTimeString,
|
|
862
|
+
validUntil: DateTimeString,
|
|
863
|
+
createdAt: DateTimeString,
|
|
864
|
+
updatedAt: DateTimeString
|
|
865
|
+
}) {};
|
|
866
|
+
/**
|
|
867
|
+
* Client-encrypted upload: the `.p12` bytes + password are sealed into
|
|
868
|
+
* `ciphertext` (the CLI parses the cert locally to fill the metadata below);
|
|
869
|
+
* the server stores the envelope and metadata and never sees the plaintext.
|
|
870
|
+
*/
|
|
871
|
+
const UploadApplePushCertificateBody = Schema.Struct({
|
|
872
|
+
id: Id,
|
|
873
|
+
...encryptedEnvelopeFields,
|
|
874
|
+
bundleIdentifier: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(200)),
|
|
875
|
+
serialNumber: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(200)),
|
|
876
|
+
appleTeamIdentifier: AppleTeamIdentifier,
|
|
877
|
+
...appleTeamMetadataFields,
|
|
878
|
+
validFrom: DateTimeString,
|
|
879
|
+
validUntil: DateTimeString
|
|
880
|
+
});
|
|
881
|
+
const DeleteApplePushCertificateResult = DeletedResult;
|
|
882
|
+
/** The encrypted envelope (relayed from R2) plus server-visible metadata; the CLI decrypts `ciphertext` to recover `{ p12Base64, p12Password }`. */
|
|
883
|
+
const DownloadApplePushCertificateResult = Schema.Struct({
|
|
884
|
+
id: Id,
|
|
885
|
+
...encryptedEnvelopeFields,
|
|
886
|
+
bundleIdentifier: Schema.String,
|
|
887
|
+
serialNumber: Schema.String,
|
|
888
|
+
appleTeamIdentifier: AppleTeamIdentifier,
|
|
889
|
+
validFrom: DateTimeString,
|
|
890
|
+
validUntil: DateTimeString
|
|
891
|
+
});
|
|
892
|
+
|
|
893
|
+
//#endregion
|
|
894
|
+
//#region ../../packages/api/src/groups/apple-push-certificates.ts
|
|
895
|
+
var ApplePushCertificatesGroup = class extends HttpApiGroup.make("applePushCertificates").add(HttpApiEndpoint.get("list", "/api/apple/push-certificates").addSuccess(Schema.Struct({ items: Schema.Array(ApplePushCertificate) })).annotateContext(OpenApi.annotations({
|
|
896
|
+
title: "List Apple push certificates",
|
|
897
|
+
description: "List APNs push SSL certificates for the organization"
|
|
898
|
+
}))).add(HttpApiEndpoint.post("upload", "/api/apple/push-certificates").setPayload(UploadApplePushCertificateBody).addSuccess(ApplePushCertificate, { status: 201 }).annotateContext(OpenApi.annotations({
|
|
899
|
+
title: "Upload push certificate",
|
|
900
|
+
description: "Upload an APNs Push Services .p12 SSL certificate"
|
|
901
|
+
}))).add(HttpApiEndpoint.del("delete")`/api/apple/push-certificates/${idParam}`.addSuccess(DeleteApplePushCertificateResult).annotateContext(OpenApi.annotations({
|
|
902
|
+
title: "Delete push certificate",
|
|
903
|
+
description: "Remove a stored APNs push SSL certificate"
|
|
904
|
+
}))).add(HttpApiEndpoint.get("download")`/api/apple/push-certificates/${idParam}/download`.addSuccess(DownloadApplePushCertificateResult).annotateContext(OpenApi.annotations({
|
|
905
|
+
title: "Download push certificate",
|
|
906
|
+
description: "Fetch the decrypted .p12 push certificate for local use (audit-logged)"
|
|
907
|
+
}))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
|
|
908
|
+
title: "Apple Push Certificates",
|
|
909
|
+
description: "Manage APNs Push Services SSL certificates"
|
|
910
|
+
})) {};
|
|
911
|
+
|
|
725
912
|
//#endregion
|
|
726
913
|
//#region ../../packages/api/src/domain/apple-push-key.ts
|
|
727
914
|
const ApplePushKeyId = tenCharPortalId("Push Key ID");
|
|
@@ -2550,7 +2737,7 @@ var WebhooksGroup = class extends HttpApiGroup.make("webhooks").add(HttpApiEndpo
|
|
|
2550
2737
|
|
|
2551
2738
|
//#endregion
|
|
2552
2739
|
//#region ../../packages/api/src/api.ts
|
|
2553
|
-
var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(EnvironmentsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(RuntimesGroup).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(UserEncryptionKeysGroup).add(OrgVaultGroup).add(MeGroup).add(WebhooksGroup).add(PoliciesGroup).add(GroupsGroup).add(PolicyAttachmentsGroup).add(ApiKeysGroup).add(InvitationsGroup).add(MembersGroup).add(OrganizationGroup).add(AdminGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
|
|
2740
|
+
var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(EnvironmentsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(RuntimesGroup).add(EnvVarsGroup).add(FingerprintsGroup).add(AuditLogsGroup).add(DevicesGroup).add(AppleTeamsGroup).add(AppleDistributionCertificatesGroup).add(ApplePushKeysGroup).add(ApplePushCertificatesGroup).add(ApplePayCertificatesGroup).add(ApplePassTypeCertificatesGroup).add(AscApiKeysGroup).add(AppleProvisioningProfilesGroup).add(GoogleServiceAccountKeysGroup).add(IosBundleConfigurationsGroup).add(IosAppMetadataGroup).add(SubmissionsGroup).add(AndroidApplicationIdentifiersGroup).add(AndroidUploadKeystoresGroup).add(AndroidBuildCredentialsGroup).add(BuildCredentialsGroup).add(UserEncryptionKeysGroup).add(OrgVaultGroup).add(MeGroup).add(WebhooksGroup).add(PoliciesGroup).add(GroupsGroup).add(PolicyAttachmentsGroup).add(ApiKeysGroup).add(InvitationsGroup).add(MembersGroup).add(OrganizationGroup).add(AdminGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
|
|
2554
2741
|
title: "Better Update Management API",
|
|
2555
2742
|
version: "1.0.0",
|
|
2556
2743
|
description: "Management API for OTA update publishing, deployment, and analytics"
|
|
@@ -3567,8 +3754,9 @@ const VaultCacheLive = Layer.effect(VaultCache, Effect.gen(function* () {
|
|
|
3567
3754
|
//#endregion
|
|
3568
3755
|
//#region src/services/version-check.ts
|
|
3569
3756
|
const NPM_REGISTRY_URL = "https://registry.npmjs.org/@better-update/cli/latest";
|
|
3570
|
-
const CACHE_TTL_MS =
|
|
3757
|
+
const CACHE_TTL_MS = 300 * 1e3;
|
|
3571
3758
|
const REFRESH_TIMEOUT_MS = 3e3;
|
|
3759
|
+
const FOREGROUND_TIMEOUT_MS = 1500;
|
|
3572
3760
|
var VersionCheck = class extends Context.Tag("cli/VersionCheck")() {};
|
|
3573
3761
|
const VersionCheckLive = Layer.effect(VersionCheck, Effect.gen(function* () {
|
|
3574
3762
|
const fs = yield* FileSystem.FileSystem;
|
|
@@ -3588,6 +3776,20 @@ const VersionCheckLive = Layer.effect(VersionCheck, Effect.gen(function* () {
|
|
|
3588
3776
|
checkedAt: parsed["checkedAt"]
|
|
3589
3777
|
};
|
|
3590
3778
|
});
|
|
3779
|
+
const fetchAndCache = (timeoutMs) => Effect.gen(function* () {
|
|
3780
|
+
const request = HttpClientRequest.get(NPM_REGISTRY_URL).pipe(HttpClientRequest.setHeader("accept", "application/json"));
|
|
3781
|
+
const response = yield* httpClient.execute(request);
|
|
3782
|
+
if (response.status < 200 || response.status >= 300) return;
|
|
3783
|
+
const body = yield* response.json;
|
|
3784
|
+
if (!isRecord$1(body) || typeof body["version"] !== "string") return;
|
|
3785
|
+
const latest = body["version"];
|
|
3786
|
+
yield* fs.makeDirectory(cacheDir, { recursive: true });
|
|
3787
|
+
yield* fs.writeFileString(cacheFile, `${JSON.stringify({
|
|
3788
|
+
latest,
|
|
3789
|
+
checkedAt: Date.now()
|
|
3790
|
+
}, null, 2)}\n`);
|
|
3791
|
+
return latest;
|
|
3792
|
+
}).pipe(Effect.timeout(timeoutMs), Effect.catchAll(() => Effect.succeed(void 0)));
|
|
3591
3793
|
return {
|
|
3592
3794
|
cachedLatest: readCache.pipe(Effect.map((entry) => entry?.latest)),
|
|
3593
3795
|
cacheStale: readCache.pipe(Effect.map((entry) => {
|
|
@@ -3595,19 +3797,8 @@ const VersionCheckLive = Layer.effect(VersionCheck, Effect.gen(function* () {
|
|
|
3595
3797
|
const elapsed = Date.now() - entry.checkedAt;
|
|
3596
3798
|
return elapsed < 0 || elapsed > CACHE_TTL_MS;
|
|
3597
3799
|
})),
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
const response = yield* httpClient.execute(request);
|
|
3601
|
-
if (response.status < 200 || response.status >= 300) return;
|
|
3602
|
-
const body = yield* response.json;
|
|
3603
|
-
if (!isRecord$1(body) || typeof body["version"] !== "string") return;
|
|
3604
|
-
const latest = body["version"];
|
|
3605
|
-
yield* fs.makeDirectory(cacheDir, { recursive: true });
|
|
3606
|
-
yield* fs.writeFileString(cacheFile, `${JSON.stringify({
|
|
3607
|
-
latest,
|
|
3608
|
-
checkedAt: Date.now()
|
|
3609
|
-
}, null, 2)}\n`);
|
|
3610
|
-
}).pipe(Effect.timeout(REFRESH_TIMEOUT_MS), Effect.catchAll(() => Effect.void))
|
|
3800
|
+
fetchLatest: fetchAndCache(FOREGROUND_TIMEOUT_MS),
|
|
3801
|
+
refreshCache: fetchAndCache(REFRESH_TIMEOUT_MS).pipe(Effect.asVoid)
|
|
3611
3802
|
};
|
|
3612
3803
|
}));
|
|
3613
3804
|
|
|
@@ -4535,6 +4726,79 @@ const writeEasJsonPatch = (projectRoot, patch) => Effect.gen(function* () {
|
|
|
4535
4726
|
return filePath;
|
|
4536
4727
|
});
|
|
4537
4728
|
/**
|
|
4729
|
+
* Default `build` profiles scaffolded by `init` / `build configure`. Mirrors the
|
|
4730
|
+
* EAS three-tier convention: `development` (dev-client, internal), `preview`
|
|
4731
|
+
* (internal QA) and `production` (store). Keep in sync with the build-profile
|
|
4732
|
+
* derivation in `build-profile.ts` (e.g. `distribution: "internal"` → ad-hoc).
|
|
4733
|
+
*/
|
|
4734
|
+
const DEFAULT_BUILD_PROFILES = {
|
|
4735
|
+
development: {
|
|
4736
|
+
developmentClient: true,
|
|
4737
|
+
distribution: "internal",
|
|
4738
|
+
channel: "development",
|
|
4739
|
+
environment: "development",
|
|
4740
|
+
android: { format: "apk" }
|
|
4741
|
+
},
|
|
4742
|
+
preview: {
|
|
4743
|
+
distribution: "internal",
|
|
4744
|
+
channel: "preview",
|
|
4745
|
+
environment: "preview",
|
|
4746
|
+
android: { format: "apk" }
|
|
4747
|
+
},
|
|
4748
|
+
production: {
|
|
4749
|
+
channel: "production",
|
|
4750
|
+
environment: "production",
|
|
4751
|
+
android: { format: "aab" }
|
|
4752
|
+
}
|
|
4753
|
+
};
|
|
4754
|
+
const DEFAULT_PROFILE_NAMES = [
|
|
4755
|
+
"development",
|
|
4756
|
+
"preview",
|
|
4757
|
+
"production"
|
|
4758
|
+
];
|
|
4759
|
+
/** Full default `eas.json` body (cli pin + the three default build profiles). */
|
|
4760
|
+
const DEFAULT_EAS_JSON = {
|
|
4761
|
+
cli: { version: ">= 7.0.0" },
|
|
4762
|
+
build: DEFAULT_BUILD_PROFILES
|
|
4763
|
+
};
|
|
4764
|
+
/**
|
|
4765
|
+
* Ensure `eas.json` carries the default build profiles. Creates the file with
|
|
4766
|
+
* the full default template when absent; otherwise tops up only the missing
|
|
4767
|
+
* default profiles, preserving every existing profile and top-level key
|
|
4768
|
+
* (`projectId`, `projectType`, `submit`, …). Never overwrites a profile that is
|
|
4769
|
+
* already defined — call sites wanting a hard reset write `DEFAULT_EAS_JSON`.
|
|
4770
|
+
*/
|
|
4771
|
+
const ensureDefaultBuildProfiles = (projectRoot) => Effect.gen(function* () {
|
|
4772
|
+
const existing = yield* readEasJsonRaw(projectRoot);
|
|
4773
|
+
if (existing === void 0) return {
|
|
4774
|
+
path: yield* writeEasJsonPatch(projectRoot, DEFAULT_EAS_JSON),
|
|
4775
|
+
action: "created",
|
|
4776
|
+
added: [...DEFAULT_PROFILE_NAMES]
|
|
4777
|
+
};
|
|
4778
|
+
const existingBuild = isRecord$1(existing["build"]) ? existing["build"] : {};
|
|
4779
|
+
const missing = DEFAULT_PROFILE_NAMES.filter((name) => !(name in existingBuild));
|
|
4780
|
+
if (missing.length === 0) return {
|
|
4781
|
+
path: easJsonPath(projectRoot),
|
|
4782
|
+
action: "noop",
|
|
4783
|
+
added: []
|
|
4784
|
+
};
|
|
4785
|
+
const additions = Object.fromEntries(missing.map((name) => [name, DEFAULT_BUILD_PROFILES[name]]));
|
|
4786
|
+
return {
|
|
4787
|
+
path: yield* writeEasJsonPatch(projectRoot, existing["cli"] === void 0 ? {
|
|
4788
|
+
cli: DEFAULT_EAS_JSON.cli,
|
|
4789
|
+
build: {
|
|
4790
|
+
...existingBuild,
|
|
4791
|
+
...additions
|
|
4792
|
+
}
|
|
4793
|
+
} : { build: {
|
|
4794
|
+
...existingBuild,
|
|
4795
|
+
...additions
|
|
4796
|
+
} }),
|
|
4797
|
+
action: "topped-up",
|
|
4798
|
+
added: missing
|
|
4799
|
+
};
|
|
4800
|
+
});
|
|
4801
|
+
/**
|
|
4538
4802
|
* Resolve the linked project id from `eas.json`'s top-level `projectId`, or
|
|
4539
4803
|
* `undefined` when the file is absent / has no usable value.
|
|
4540
4804
|
*/
|
|
@@ -22076,6 +22340,51 @@ const runAndroidBuild = (input) => Effect.gen(function* () {
|
|
|
22076
22340
|
return input.strategy === "custom" ? yield* runAndroidCustom(input, commandEnv) : yield* runGradleBuild(input, commandEnv);
|
|
22077
22341
|
});
|
|
22078
22342
|
|
|
22343
|
+
//#endregion
|
|
22344
|
+
//#region src/lib/credential-choices.ts
|
|
22345
|
+
/** ISO timestamp → `YYYY-MM-DD` for compact, scannable labels. */
|
|
22346
|
+
const isoDate = (value) => value.slice(0, 10);
|
|
22347
|
+
/**
|
|
22348
|
+
* Credentials store the internal team UUID, which is meaningless to read. Build a
|
|
22349
|
+
* resolver from `appleTeams.list()` that maps it to the team name (falling back to
|
|
22350
|
+
* the 10-char portal identifier, then the raw id if the team isn't in the list).
|
|
22351
|
+
*/
|
|
22352
|
+
const makeAppleTeamLabeler = (teams) => {
|
|
22353
|
+
const byId = new Map(teams.map((team) => [team.id, team.name ?? team.appleTeamId]));
|
|
22354
|
+
return (internalTeamId) => byId.get(internalTeamId) ?? internalTeamId;
|
|
22355
|
+
};
|
|
22356
|
+
/**
|
|
22357
|
+
* Keystore aliases are often cryptic, so surface the type + creation date in the
|
|
22358
|
+
* label and the SHA-1 fingerprint (which matches the Play Console upload key) on
|
|
22359
|
+
* the active-row hint.
|
|
22360
|
+
*/
|
|
22361
|
+
const keystoreChoice = (item) => {
|
|
22362
|
+
const details = [item.keystoreType, `created ${isoDate(item.createdAt)}`].filter((part) => part !== null);
|
|
22363
|
+
return {
|
|
22364
|
+
value: item.id,
|
|
22365
|
+
label: `${item.keyAlias} (${details.join(", ")})`,
|
|
22366
|
+
hint: item.sha1Fingerprint ? `SHA-1 ${item.sha1Fingerprint}` : `id ${item.id.slice(0, 8)}…`
|
|
22367
|
+
};
|
|
22368
|
+
};
|
|
22369
|
+
/**
|
|
22370
|
+
* Push keys share a team, so include the creation date to tell siblings apart.
|
|
22371
|
+
* Pass a `teamLabel` (see {@link makeAppleTeamLabeler}) to show the team name
|
|
22372
|
+
* instead of the internal UUID.
|
|
22373
|
+
*/
|
|
22374
|
+
const pushKeyChoice = (key, teamLabel = key.appleTeamId) => ({
|
|
22375
|
+
value: key.id,
|
|
22376
|
+
label: `${key.keyId} (team ${teamLabel}, added ${isoDate(key.createdAt)})`
|
|
22377
|
+
});
|
|
22378
|
+
/**
|
|
22379
|
+
* Surface the expiry so an expired certificate is obvious before it's picked.
|
|
22380
|
+
* Pass a `teamLabel` (see {@link makeAppleTeamLabeler}) to show the team name
|
|
22381
|
+
* instead of the internal UUID.
|
|
22382
|
+
*/
|
|
22383
|
+
const distributionCertChoice = (cert, teamLabel = cert.appleTeamId) => ({
|
|
22384
|
+
value: cert.id,
|
|
22385
|
+
label: `${cert.serialNumber.slice(0, 12)}… (team ${teamLabel}, exp ${isoDate(cert.validUntil)})`
|
|
22386
|
+
});
|
|
22387
|
+
|
|
22079
22388
|
//#endregion
|
|
22080
22389
|
//#region src/lib/credentials-generator-apple-id.ts
|
|
22081
22390
|
const DISTRIBUTION_TO_PROFILE_TYPE = {
|
|
@@ -22443,10 +22752,7 @@ const chooseDistributionCertViaAppleId = (api, ctx, appleTeamIdentifier) => Effe
|
|
|
22443
22752
|
const choice = yield* promptSelect("Select a distribution certificate (or 'generate' for a fresh one)", [{
|
|
22444
22753
|
value: GENERATE_NEW,
|
|
22445
22754
|
label: "Generate a new distribution certificate"
|
|
22446
|
-
}, ...items.map((cert) => (
|
|
22447
|
-
value: cert.id,
|
|
22448
|
-
label: `${cert.serialNumber.slice(0, 12)}… (team ${appleTeamIdentifier})`
|
|
22449
|
-
}))]);
|
|
22755
|
+
}, ...items.map((cert) => distributionCertChoice(cert, team?.name ?? appleTeamIdentifier))]);
|
|
22450
22756
|
if (choice === GENERATE_NEW) {
|
|
22451
22757
|
const created = yield* generateDistributionCertViaAppleIdInteractive(api, ctx);
|
|
22452
22758
|
return {
|
|
@@ -22557,13 +22863,11 @@ const chooseIosCertificateId = (api) => Effect.gen(function* () {
|
|
|
22557
22863
|
});
|
|
22558
22864
|
return (yield* generateDistributionCertInteractive(api)).id;
|
|
22559
22865
|
}
|
|
22866
|
+
const teamLabel = makeAppleTeamLabeler((yield* api.appleTeams.list()).items);
|
|
22560
22867
|
const choice = yield* promptSelect("Select a distribution certificate (or 'generate' for a fresh one)", [{
|
|
22561
22868
|
value: "__generate__",
|
|
22562
22869
|
label: "Generate a new distribution certificate"
|
|
22563
|
-
}, ...certs.items.map((cert) => (
|
|
22564
|
-
value: cert.id,
|
|
22565
|
-
label: `${cert.serialNumber.slice(0, 12)}… (team ${cert.appleTeamId})`
|
|
22566
|
-
}))]);
|
|
22870
|
+
}, ...certs.items.map((cert) => distributionCertChoice(cert, teamLabel(cert.appleTeamId)))]);
|
|
22567
22871
|
if (choice === "__generate__") return (yield* generateDistributionCertInteractive(api)).id;
|
|
22568
22872
|
return choice;
|
|
22569
22873
|
});
|
|
@@ -22665,10 +22969,7 @@ const pickExistingKeystore = (api) => Effect.gen(function* () {
|
|
|
22665
22969
|
message: "No existing keystores in this organization.",
|
|
22666
22970
|
hint: "Re-run and choose 'Generate new keystore'."
|
|
22667
22971
|
});
|
|
22668
|
-
return yield* promptSelect("Select a keystore", keystores.items.map(
|
|
22669
|
-
value: item.id,
|
|
22670
|
-
label: item.keyAlias
|
|
22671
|
-
})));
|
|
22972
|
+
return yield* promptSelect("Select a keystore", keystores.items.map(keystoreChoice));
|
|
22672
22973
|
});
|
|
22673
22974
|
const resolveAndroidAppId = (api, input) => Effect.gen(function* () {
|
|
22674
22975
|
const existing = (yield* api.androidApplicationIdentifiers.list({ path: { projectId: input.projectId } })).items.find((item) => item.packageName === input.applicationIdentifier);
|
|
@@ -24173,34 +24474,6 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
|
|
|
24173
24474
|
|
|
24174
24475
|
//#endregion
|
|
24175
24476
|
//#region src/commands/build/configure.ts
|
|
24176
|
-
const DEFAULT_EAS_JSON = {
|
|
24177
|
-
cli: { version: ">= 7.0.0" },
|
|
24178
|
-
build: {
|
|
24179
|
-
development: {
|
|
24180
|
-
developmentClient: true,
|
|
24181
|
-
distribution: "internal",
|
|
24182
|
-
channel: "development",
|
|
24183
|
-
environment: "development",
|
|
24184
|
-
android: { format: "apk" }
|
|
24185
|
-
},
|
|
24186
|
-
preview: {
|
|
24187
|
-
distribution: "internal",
|
|
24188
|
-
channel: "preview",
|
|
24189
|
-
environment: "preview",
|
|
24190
|
-
android: { format: "apk" }
|
|
24191
|
-
},
|
|
24192
|
-
production: {
|
|
24193
|
-
channel: "production",
|
|
24194
|
-
environment: "production",
|
|
24195
|
-
android: { format: "aab" }
|
|
24196
|
-
}
|
|
24197
|
-
}
|
|
24198
|
-
};
|
|
24199
|
-
const DEFAULT_PROFILES = [
|
|
24200
|
-
"development",
|
|
24201
|
-
"preview",
|
|
24202
|
-
"production"
|
|
24203
|
-
];
|
|
24204
24477
|
const writeEasJson = (filePath, value) => Effect.gen(function* () {
|
|
24205
24478
|
yield* (yield* FileSystem.FileSystem).writeFileString(filePath, `${JSON.stringify(value, null, 2)}\n`).pipe(Effect.mapError((cause) => new BuildProfileError({ message: `Failed to write eas.json: ${cause.message}` })));
|
|
24206
24479
|
});
|
|
@@ -24216,43 +24489,44 @@ const configureBuildCommand = defineCommand({
|
|
|
24216
24489
|
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
24217
24490
|
const { allow: interactive } = yield* InteractiveMode;
|
|
24218
24491
|
const projectRoot = yield* (yield* CliRuntime).cwd;
|
|
24219
|
-
const
|
|
24492
|
+
const filePath = easJsonPath(projectRoot);
|
|
24220
24493
|
const fs = yield* FileSystem.FileSystem;
|
|
24221
|
-
|
|
24222
|
-
yield* writeEasJson(easJsonPath, DEFAULT_EAS_JSON);
|
|
24223
|
-
yield* printHuman(`Wrote eas.json with default profiles to ${easJsonPath}.`);
|
|
24224
|
-
yield* printHumanKeyValue([["Profiles", DEFAULT_PROFILES.join(", ")], ["Path", easJsonPath]]);
|
|
24225
|
-
return {
|
|
24226
|
-
action: "created",
|
|
24227
|
-
path: easJsonPath,
|
|
24228
|
-
profiles: [...DEFAULT_PROFILES]
|
|
24229
|
-
};
|
|
24230
|
-
}
|
|
24494
|
+
const exists = yield* fs.exists(filePath);
|
|
24231
24495
|
if (args.force === true) {
|
|
24232
|
-
if (!(interactive ? yield* promptConfirm(`Overwrite existing eas.json at ${
|
|
24496
|
+
if (!(exists && interactive ? yield* promptConfirm(`Overwrite existing eas.json at ${filePath} with defaults?`) : true)) {
|
|
24233
24497
|
yield* printHuman("Aborted. eas.json was not modified.");
|
|
24234
24498
|
return {
|
|
24235
24499
|
action: "aborted",
|
|
24236
|
-
path:
|
|
24500
|
+
path: filePath
|
|
24237
24501
|
};
|
|
24238
24502
|
}
|
|
24239
|
-
yield* writeEasJson(
|
|
24240
|
-
yield* printHuman(
|
|
24503
|
+
yield* writeEasJson(filePath, DEFAULT_EAS_JSON);
|
|
24504
|
+
yield* printHuman(exists ? "Overwrote eas.json with default profiles." : `Wrote eas.json with default profiles to ${filePath}.`);
|
|
24241
24505
|
return {
|
|
24242
|
-
action: "overwritten",
|
|
24243
|
-
path:
|
|
24244
|
-
profiles: [...
|
|
24506
|
+
action: exists ? "overwritten" : "created",
|
|
24507
|
+
path: filePath,
|
|
24508
|
+
profiles: [...DEFAULT_PROFILE_NAMES]
|
|
24245
24509
|
};
|
|
24246
24510
|
}
|
|
24247
|
-
|
|
24511
|
+
if (!exists) {
|
|
24512
|
+
const created = yield* ensureDefaultBuildProfiles(projectRoot);
|
|
24513
|
+
yield* printHuman(`Wrote eas.json with default profiles to ${created.path}.`);
|
|
24514
|
+
yield* printHumanKeyValue([["Profiles", created.added.join(", ")], ["Path", created.path]]);
|
|
24515
|
+
return {
|
|
24516
|
+
action: "created",
|
|
24517
|
+
path: created.path,
|
|
24518
|
+
profiles: created.added
|
|
24519
|
+
};
|
|
24520
|
+
}
|
|
24521
|
+
const config = yield* parseEasConfig(yield* fs.readFileString(filePath).pipe(Effect.mapError((cause) => new BuildProfileError({ message: `Failed to read eas.json: ${cause.message}` }))));
|
|
24248
24522
|
const existingProfiles = Object.keys(config.build ?? {});
|
|
24249
|
-
const missing =
|
|
24523
|
+
const missing = DEFAULT_PROFILE_NAMES.filter((name) => !existingProfiles.includes(name));
|
|
24250
24524
|
if (missing.length === 0) {
|
|
24251
24525
|
yield* printHuman(`eas.json already defines all default profiles (${existingProfiles.join(", ")}). Nothing to add.`);
|
|
24252
24526
|
yield* printHuman("Pass --force to overwrite with the default template.");
|
|
24253
24527
|
return {
|
|
24254
24528
|
action: "noop",
|
|
24255
|
-
path:
|
|
24529
|
+
path: filePath,
|
|
24256
24530
|
existing: existingProfiles
|
|
24257
24531
|
};
|
|
24258
24532
|
}
|
|
@@ -24260,28 +24534,21 @@ const configureBuildCommand = defineCommand({
|
|
|
24260
24534
|
yield* printHuman("Aborted. eas.json was not modified.");
|
|
24261
24535
|
return {
|
|
24262
24536
|
action: "aborted",
|
|
24263
|
-
path:
|
|
24537
|
+
path: filePath
|
|
24264
24538
|
};
|
|
24265
24539
|
}
|
|
24266
|
-
const
|
|
24267
|
-
yield*
|
|
24268
|
-
build: {
|
|
24269
|
-
...config.build,
|
|
24270
|
-
...additions
|
|
24271
|
-
},
|
|
24272
|
-
...compact({ cli: config.cli })
|
|
24273
|
-
});
|
|
24274
|
-
yield* printHuman(`Added profile(s) to eas.json: ${missing.join(", ")}.`);
|
|
24540
|
+
const result = yield* ensureDefaultBuildProfiles(projectRoot);
|
|
24541
|
+
yield* printHuman(`Added profile(s) to eas.json: ${result.added.join(", ")}.`);
|
|
24275
24542
|
yield* printHumanKeyValue([
|
|
24276
24543
|
["Existing", existingProfiles.join(", ") || "(none)"],
|
|
24277
|
-
["Added",
|
|
24278
|
-
["Path",
|
|
24544
|
+
["Added", result.added.join(", ")],
|
|
24545
|
+
["Path", result.path]
|
|
24279
24546
|
]);
|
|
24280
24547
|
return {
|
|
24281
24548
|
action: "topped-up",
|
|
24282
|
-
path:
|
|
24549
|
+
path: result.path,
|
|
24283
24550
|
existing: existingProfiles,
|
|
24284
|
-
added:
|
|
24551
|
+
added: result.added
|
|
24285
24552
|
};
|
|
24286
24553
|
}), { json: "value" })
|
|
24287
24554
|
});
|
|
@@ -25821,13 +26088,160 @@ const inspectP12 = (params) => Effect.try({
|
|
|
25821
26088
|
catch: (error) => new CredentialValidationError({ message: `Failed to parse P12 certificate: ${error instanceof Error ? error.message : String(error)}` })
|
|
25822
26089
|
});
|
|
25823
26090
|
|
|
26091
|
+
//#endregion
|
|
26092
|
+
//#region src/lib/credentials-pass-type-certificate.ts
|
|
26093
|
+
/**
|
|
26094
|
+
* Manual upload of a Wallet Pass Type ID `.p12` certificate. The Pass Type ID
|
|
26095
|
+
* (`pass.*`) is passed explicitly; serial/validity come from the parsed cert and
|
|
26096
|
+
* the team from its OU (or `--apple-team-identifier`). Only
|
|
26097
|
+
* `{ p12Base64, p12Password }` is sealed.
|
|
26098
|
+
*/
|
|
26099
|
+
const uploadIosPassTypeCertificate = (api, input, bytes) => Effect.gen(function* () {
|
|
26100
|
+
if (input.password === void 0) return yield* new CredentialValidationError({ message: "Missing --password required for the selected credential type." });
|
|
26101
|
+
if (!input.passTypeIdentifier) return yield* new CredentialValidationError({ message: "Missing --pass-type-identifier required for a Pass Type ID certificate." });
|
|
26102
|
+
const info = yield* inspectP12({
|
|
26103
|
+
data: Buffer.from(bytes),
|
|
26104
|
+
password: input.password
|
|
26105
|
+
});
|
|
26106
|
+
const appleTeamIdentifier = info.teamId ?? input.appleTeamIdentifier;
|
|
26107
|
+
if (!appleTeamIdentifier) return yield* new CredentialValidationError({ message: "Could not derive Apple Team ID from the certificate; pass --apple-team-identifier." });
|
|
26108
|
+
if (!info.validFrom || !info.expiresAt) return yield* new CredentialValidationError({ message: "Certificate is missing notBefore/notAfter dates." });
|
|
26109
|
+
const metadata = {
|
|
26110
|
+
passTypeIdentifier: input.passTypeIdentifier,
|
|
26111
|
+
serialNumber: info.serialNumber,
|
|
26112
|
+
appleTeamIdentifier,
|
|
26113
|
+
validFrom: info.validFrom.toISOString(),
|
|
26114
|
+
validUntil: info.expiresAt.toISOString()
|
|
26115
|
+
};
|
|
26116
|
+
const envelope = yield* sealForUpload({
|
|
26117
|
+
session: yield* openVaultSessionInteractive(api),
|
|
26118
|
+
credentialType: "pass-type-certificate",
|
|
26119
|
+
metadata,
|
|
26120
|
+
secret: {
|
|
26121
|
+
p12Base64: toBase64(bytes),
|
|
26122
|
+
p12Password: input.password
|
|
26123
|
+
}
|
|
26124
|
+
});
|
|
26125
|
+
return {
|
|
26126
|
+
id: (yield* api.applePassTypeCertificates.upload({ payload: {
|
|
26127
|
+
...toUploadEnvelope(envelope),
|
|
26128
|
+
...metadata
|
|
26129
|
+
} })).id,
|
|
26130
|
+
name: input.name,
|
|
26131
|
+
platform: "ios",
|
|
26132
|
+
type: "pass-type-certificate"
|
|
26133
|
+
};
|
|
26134
|
+
});
|
|
26135
|
+
|
|
26136
|
+
//#endregion
|
|
26137
|
+
//#region src/lib/credentials-pay-certificate.ts
|
|
26138
|
+
/**
|
|
26139
|
+
* Manual upload of an Apple Pay payment-processing `.p12` certificate. The
|
|
26140
|
+
* Merchant ID (`merchant.*`) is not carried reliably in the cert, so it is passed
|
|
26141
|
+
* explicitly; serial/validity come from the parsed cert and the team from its OU
|
|
26142
|
+
* (or `--apple-team-identifier`). Only `{ p12Base64, p12Password }` is sealed.
|
|
26143
|
+
*/
|
|
26144
|
+
const uploadIosPayCertificate = (api, input, bytes) => Effect.gen(function* () {
|
|
26145
|
+
if (input.password === void 0) return yield* new CredentialValidationError({ message: "Missing --password required for the selected credential type." });
|
|
26146
|
+
if (!input.merchantIdentifier) return yield* new CredentialValidationError({ message: "Missing --merchant-identifier required for an Apple Pay certificate." });
|
|
26147
|
+
const info = yield* inspectP12({
|
|
26148
|
+
data: Buffer.from(bytes),
|
|
26149
|
+
password: input.password
|
|
26150
|
+
});
|
|
26151
|
+
const appleTeamIdentifier = info.teamId ?? input.appleTeamIdentifier;
|
|
26152
|
+
if (!appleTeamIdentifier) return yield* new CredentialValidationError({ message: "Could not derive Apple Team ID from the certificate; pass --apple-team-identifier." });
|
|
26153
|
+
if (!info.validFrom || !info.expiresAt) return yield* new CredentialValidationError({ message: "Certificate is missing notBefore/notAfter dates." });
|
|
26154
|
+
const metadata = {
|
|
26155
|
+
merchantIdentifier: input.merchantIdentifier,
|
|
26156
|
+
serialNumber: info.serialNumber,
|
|
26157
|
+
appleTeamIdentifier,
|
|
26158
|
+
validFrom: info.validFrom.toISOString(),
|
|
26159
|
+
validUntil: info.expiresAt.toISOString()
|
|
26160
|
+
};
|
|
26161
|
+
const envelope = yield* sealForUpload({
|
|
26162
|
+
session: yield* openVaultSessionInteractive(api),
|
|
26163
|
+
credentialType: "apple-pay-certificate",
|
|
26164
|
+
metadata,
|
|
26165
|
+
secret: {
|
|
26166
|
+
p12Base64: toBase64(bytes),
|
|
26167
|
+
p12Password: input.password
|
|
26168
|
+
}
|
|
26169
|
+
});
|
|
26170
|
+
return {
|
|
26171
|
+
id: (yield* api.applePayCertificates.upload({ payload: {
|
|
26172
|
+
...toUploadEnvelope(envelope),
|
|
26173
|
+
...metadata
|
|
26174
|
+
} })).id,
|
|
26175
|
+
name: input.name,
|
|
26176
|
+
platform: "ios",
|
|
26177
|
+
type: "apple-pay-certificate"
|
|
26178
|
+
};
|
|
26179
|
+
});
|
|
26180
|
+
|
|
26181
|
+
//#endregion
|
|
26182
|
+
//#region src/lib/credentials-push-certificate.ts
|
|
26183
|
+
/**
|
|
26184
|
+
* Derive the App ID a push SSL cert is bound to from its Common Name, e.g.
|
|
26185
|
+
* "Apple Push Services: com.example.app" → "com.example.app". Returns undefined
|
|
26186
|
+
* when the CN does not carry a reverse-DNS identifier.
|
|
26187
|
+
*/
|
|
26188
|
+
const bundleIdFromPushCertCN = (commonName) => {
|
|
26189
|
+
const bundle = /:\s*(?<bundle>[A-Za-z0-9](?:[A-Za-z0-9.-]*[A-Za-z0-9])?)\s*$/u.exec(commonName)?.groups?.["bundle"];
|
|
26190
|
+
return bundle?.includes(".") ? bundle : void 0;
|
|
26191
|
+
};
|
|
26192
|
+
/**
|
|
26193
|
+
* Manual upload of a legacy APNs Push Services `.p12` SSL certificate. The CLI
|
|
26194
|
+
* parses the cert locally for its metadata (serial, validity, team, and the App
|
|
26195
|
+
* ID from the CN) and seals `{ p12Base64, p12Password }` into the vault; the
|
|
26196
|
+
* server only ever stores the ciphertext.
|
|
26197
|
+
*/
|
|
26198
|
+
const uploadIosPushCertificate = (api, input, bytes) => Effect.gen(function* () {
|
|
26199
|
+
if (input.password === void 0) return yield* new CredentialValidationError({ message: "Missing --password required for the selected credential type." });
|
|
26200
|
+
const info = yield* inspectP12({
|
|
26201
|
+
data: Buffer.from(bytes),
|
|
26202
|
+
password: input.password
|
|
26203
|
+
});
|
|
26204
|
+
if (!info.teamId) return yield* new CredentialValidationError({ message: "Could not derive Apple Team ID from certificate subject (expected OU=TEAMID or CN with (TEAMID))." });
|
|
26205
|
+
if (!info.validFrom || !info.expiresAt) return yield* new CredentialValidationError({ message: "Certificate is missing notBefore/notAfter dates." });
|
|
26206
|
+
const bundleIdentifier = input.bundleIdentifier ?? bundleIdFromPushCertCN(info.signingIdentity);
|
|
26207
|
+
if (!bundleIdentifier) return yield* new CredentialValidationError({ message: "Could not derive the App ID from the push certificate (expected CN 'Apple Push Services: <bundle id>'). Pass --bundle-identifier." });
|
|
26208
|
+
const metadata = {
|
|
26209
|
+
bundleIdentifier,
|
|
26210
|
+
serialNumber: info.serialNumber,
|
|
26211
|
+
appleTeamIdentifier: info.teamId,
|
|
26212
|
+
validFrom: info.validFrom.toISOString(),
|
|
26213
|
+
validUntil: info.expiresAt.toISOString()
|
|
26214
|
+
};
|
|
26215
|
+
const envelope = yield* sealForUpload({
|
|
26216
|
+
session: yield* openVaultSessionInteractive(api),
|
|
26217
|
+
credentialType: "push-certificate",
|
|
26218
|
+
metadata,
|
|
26219
|
+
secret: {
|
|
26220
|
+
p12Base64: toBase64(bytes),
|
|
26221
|
+
p12Password: input.password
|
|
26222
|
+
}
|
|
26223
|
+
});
|
|
26224
|
+
return {
|
|
26225
|
+
id: (yield* api.applePushCertificates.upload({ payload: {
|
|
26226
|
+
...toUploadEnvelope(envelope),
|
|
26227
|
+
...metadata
|
|
26228
|
+
} })).id,
|
|
26229
|
+
name: input.name,
|
|
26230
|
+
platform: "ios",
|
|
26231
|
+
type: "push-certificate"
|
|
26232
|
+
};
|
|
26233
|
+
});
|
|
26234
|
+
|
|
25824
26235
|
//#endregion
|
|
25825
26236
|
//#region src/lib/credentials-manager.ts
|
|
25826
26237
|
const formatDistribution = (value) => value.toLowerCase().replaceAll("_", "-");
|
|
25827
26238
|
const listAllCredentials = (api) => Effect.gen(function* () {
|
|
25828
|
-
const [certs, pushKeys, ascKeys, profiles, keystores, googleKeys] = yield* Effect.all([
|
|
26239
|
+
const [certs, pushKeys, pushCerts, payCerts, passCerts, ascKeys, profiles, keystores, googleKeys] = yield* Effect.all([
|
|
25829
26240
|
api.appleDistributionCertificates.list(),
|
|
25830
26241
|
api.applePushKeys.list(),
|
|
26242
|
+
api.applePushCertificates.list(),
|
|
26243
|
+
api.applePayCertificates.list(),
|
|
26244
|
+
api.applePassTypeCertificates.list(),
|
|
25831
26245
|
api.ascApiKeys.list(),
|
|
25832
26246
|
api.appleProvisioningProfiles.list({ urlParams: {} }),
|
|
25833
26247
|
api.androidUploadKeystores.list(),
|
|
@@ -25848,6 +26262,27 @@ const listAllCredentials = (api) => Effect.gen(function* () {
|
|
|
25848
26262
|
type: "push-key",
|
|
25849
26263
|
distribution: null
|
|
25850
26264
|
})),
|
|
26265
|
+
...pushCerts.items.map((cert) => ({
|
|
26266
|
+
id: cert.id,
|
|
26267
|
+
name: cert.bundleIdentifier,
|
|
26268
|
+
platform: "ios",
|
|
26269
|
+
type: "push-certificate",
|
|
26270
|
+
distribution: null
|
|
26271
|
+
})),
|
|
26272
|
+
...payCerts.items.map((cert) => ({
|
|
26273
|
+
id: cert.id,
|
|
26274
|
+
name: cert.merchantIdentifier,
|
|
26275
|
+
platform: "ios",
|
|
26276
|
+
type: "apple-pay-certificate",
|
|
26277
|
+
distribution: null
|
|
26278
|
+
})),
|
|
26279
|
+
...passCerts.items.map((cert) => ({
|
|
26280
|
+
id: cert.id,
|
|
26281
|
+
name: cert.passTypeIdentifier,
|
|
26282
|
+
platform: "ios",
|
|
26283
|
+
type: "pass-type-certificate",
|
|
26284
|
+
distribution: null
|
|
26285
|
+
})),
|
|
25851
26286
|
...ascKeys.items.map((key) => ({
|
|
25852
26287
|
id: key.id,
|
|
25853
26288
|
name: key.name,
|
|
@@ -26045,6 +26480,9 @@ const uploadAndroidGoogleServiceAccountKey = (api, input, bytes) => Effect.gen(f
|
|
|
26045
26480
|
const uploadHandlers = {
|
|
26046
26481
|
"ios:distribution-certificate": uploadIosDistributionCertificate,
|
|
26047
26482
|
"ios:push-key": uploadIosPushKey,
|
|
26483
|
+
"ios:push-certificate": uploadIosPushCertificate,
|
|
26484
|
+
"ios:apple-pay-certificate": uploadIosPayCertificate,
|
|
26485
|
+
"ios:pass-type-certificate": uploadIosPassTypeCertificate,
|
|
26048
26486
|
"ios:asc-api-key": uploadIosAscApiKey,
|
|
26049
26487
|
"ios:provisioning-profile": uploadIosProvisioningProfile,
|
|
26050
26488
|
"android:keystore": uploadAndroidKeystore,
|
|
@@ -26070,6 +26508,15 @@ const deleteCredential = (api, input) => {
|
|
|
26070
26508
|
platform: "ios",
|
|
26071
26509
|
type: "push-key"
|
|
26072
26510
|
}, () => api.applePushKeys.delete({ path })), Match.when({
|
|
26511
|
+
platform: "ios",
|
|
26512
|
+
type: "push-certificate"
|
|
26513
|
+
}, () => api.applePushCertificates.delete({ path })), Match.when({
|
|
26514
|
+
platform: "ios",
|
|
26515
|
+
type: "apple-pay-certificate"
|
|
26516
|
+
}, () => api.applePayCertificates.delete({ path })), Match.when({
|
|
26517
|
+
platform: "ios",
|
|
26518
|
+
type: "pass-type-certificate"
|
|
26519
|
+
}, () => api.applePassTypeCertificates.delete({ path })), Match.when({
|
|
26073
26520
|
platform: "ios",
|
|
26074
26521
|
type: "asc-api-key"
|
|
26075
26522
|
}, () => api.ascApiKeys.delete({ path })), Match.when({
|
|
@@ -26150,6 +26597,9 @@ const TYPE_LABELS = {
|
|
|
26150
26597
|
"distribution-certificate": "iOS distribution certificate",
|
|
26151
26598
|
"provisioning-profile": "iOS provisioning profile",
|
|
26152
26599
|
"push-key": "APNs push key",
|
|
26600
|
+
"push-certificate": "APNs push SSL certificate",
|
|
26601
|
+
"apple-pay-certificate": "Apple Pay certificate",
|
|
26602
|
+
"pass-type-certificate": "Pass Type ID certificate",
|
|
26153
26603
|
"asc-api-key": "ASC API key",
|
|
26154
26604
|
keystore: "Android keystore",
|
|
26155
26605
|
"google-service-account-key": "Google service account key"
|
|
@@ -26365,10 +26815,7 @@ const changeDefaultKeystore = (ctx) => Effect.gen(function* () {
|
|
|
26365
26815
|
const downloadAndroidKeystoreInteractive = (ctx) => Effect.gen(function* () {
|
|
26366
26816
|
const list = yield* ctx.api.androidUploadKeystores.list();
|
|
26367
26817
|
if (list.items.length === 0) return yield* Console.log("No keystores to download.");
|
|
26368
|
-
const id = yield* promptSelect("Select a keystore to download", list.items.map(
|
|
26369
|
-
value: item.id,
|
|
26370
|
-
label: `${item.keyAlias} (${item.id.slice(0, 8)}…)`
|
|
26371
|
-
})));
|
|
26818
|
+
const id = yield* promptSelect("Select a keystore to download", list.items.map(keystoreChoice));
|
|
26372
26819
|
const data = yield* ctx.api.androidUploadKeystores.download({ path: { id } });
|
|
26373
26820
|
const secret = yield* openFromDownload({
|
|
26374
26821
|
session: yield* openVaultSessionInteractive(ctx.api),
|
|
@@ -26692,10 +27139,8 @@ const revokeIosDistributionCert = (ctx) => Effect.gen(function* () {
|
|
|
26692
27139
|
message: "No ASC API key linked to an Apple team.",
|
|
26693
27140
|
hint: "Upload an ASC API key first so the CLI can call Apple to revoke."
|
|
26694
27141
|
});
|
|
26695
|
-
const
|
|
26696
|
-
|
|
26697
|
-
label: `${cert.serialNumber.slice(0, 12)}… (team ${cert.appleTeamId})`
|
|
26698
|
-
})));
|
|
27142
|
+
const teamLabel = makeAppleTeamLabeler((yield* ctx.api.appleTeams.list()).items);
|
|
27143
|
+
const localId = yield* promptSelect("Select a distribution certificate to revoke", certs.items.map((cert) => distributionCertChoice(cert, teamLabel(cert.appleTeamId))));
|
|
26699
27144
|
const target = certs.items.find((entry) => entry.id === localId);
|
|
26700
27145
|
const ascApiKeyId = teamKeys.find((key) => key.appleTeamId === target?.appleTeamId)?.id ?? (yield* promptSelect("Select an ASC API key to call Apple with", teamKeys.map((key) => ({
|
|
26701
27146
|
value: key.id,
|
|
@@ -26722,10 +27167,8 @@ const revokeIosPushKey = (ctx) => Effect.gen(function* () {
|
|
|
26722
27167
|
message: "No APNs push keys in this account.",
|
|
26723
27168
|
hint: "Run 'Add a new push key' to create one first."
|
|
26724
27169
|
});
|
|
26725
|
-
const
|
|
26726
|
-
|
|
26727
|
-
label: `${key.keyId} (team ${key.appleTeamId})`
|
|
26728
|
-
})));
|
|
27170
|
+
const teamLabel = makeAppleTeamLabeler((yield* ctx.api.appleTeams.list()).items);
|
|
27171
|
+
const localId = yield* promptSelect("Select a push key to revoke", items.map((key) => pushKeyChoice(key, teamLabel(key.appleTeamId))));
|
|
26729
27172
|
const target = items.find((entry) => entry.id === localId);
|
|
26730
27173
|
if (target === void 0) return yield* new MissingCredentialsError({
|
|
26731
27174
|
message: `Selected push key ${localId} not found.`,
|
|
@@ -26863,10 +27306,8 @@ const bindIosPushKey = (ctx) => Effect.gen(function* () {
|
|
|
26863
27306
|
hint: "Run 'Add a new push key' first."
|
|
26864
27307
|
});
|
|
26865
27308
|
const config = yield* promptForBundleConfig(ctx);
|
|
26866
|
-
const
|
|
26867
|
-
|
|
26868
|
-
label: `${key.keyId} (team ${key.appleTeamId})`
|
|
26869
|
-
})));
|
|
27309
|
+
const teamLabel = makeAppleTeamLabeler((yield* ctx.api.appleTeams.list()).items);
|
|
27310
|
+
const pushKeyId = yield* promptSelect("Select a push key to bind", keys.items.map((key) => pushKeyChoice(key, teamLabel(key.appleTeamId))));
|
|
26870
27311
|
yield* ctx.api.iosBundleConfigurations.update({
|
|
26871
27312
|
path: { id: config.id },
|
|
26872
27313
|
payload: { applePushKeyId: pushKeyId }
|
|
@@ -26883,10 +27324,8 @@ const setupProjectPushNotifications = (ctx) => Effect.gen(function* () {
|
|
|
26883
27324
|
label: "Add a new push key"
|
|
26884
27325
|
}]);
|
|
26885
27326
|
const config = yield* promptForBundleConfig(ctx);
|
|
26886
|
-
const
|
|
26887
|
-
|
|
26888
|
-
label: `${key.keyId} (team ${key.appleTeamId})`
|
|
26889
|
-
})));
|
|
27327
|
+
const teamLabel = makeAppleTeamLabeler(keys.items.length === 0 ? [] : (yield* ctx.api.appleTeams.list()).items);
|
|
27328
|
+
const pushKeyId = choice === "add" ? yield* createNewPushKeyForBundle(ctx, config.appleTeamId) : yield* promptSelect("Select a push key to bind", keys.items.map((key) => pushKeyChoice(key, teamLabel(key.appleTeamId))));
|
|
26890
27329
|
yield* ctx.api.iosBundleConfigurations.update({
|
|
26891
27330
|
path: { id: config.id },
|
|
26892
27331
|
payload: { applePushKeyId: pushKeyId }
|
|
@@ -27648,6 +28087,9 @@ const CREDENTIAL_TYPES$3 = [
|
|
|
27648
28087
|
"distribution-certificate",
|
|
27649
28088
|
"provisioning-profile",
|
|
27650
28089
|
"push-key",
|
|
28090
|
+
"push-certificate",
|
|
28091
|
+
"apple-pay-certificate",
|
|
28092
|
+
"pass-type-certificate",
|
|
27651
28093
|
"asc-api-key",
|
|
27652
28094
|
"keystore",
|
|
27653
28095
|
"google-service-account-key"
|
|
@@ -27764,6 +28206,9 @@ const DOWNLOAD_TYPES = [
|
|
|
27764
28206
|
"distribution-certificate",
|
|
27765
28207
|
"provisioning-profile",
|
|
27766
28208
|
"push-key",
|
|
28209
|
+
"push-certificate",
|
|
28210
|
+
"apple-pay-certificate",
|
|
28211
|
+
"pass-type-certificate",
|
|
27767
28212
|
"asc-api-key",
|
|
27768
28213
|
"keystore",
|
|
27769
28214
|
"google-service-account-key"
|
|
@@ -27855,6 +28300,105 @@ const downloadPushKey = ({ api, id, cwd, output }) => Effect.gen(function* () {
|
|
|
27855
28300
|
}
|
|
27856
28301
|
};
|
|
27857
28302
|
});
|
|
28303
|
+
const downloadPushCertificate = ({ api, id, cwd, output }) => Effect.gen(function* () {
|
|
28304
|
+
const data = yield* api.applePushCertificates.download({ path: { id } });
|
|
28305
|
+
const secret = yield* openFromDownload({
|
|
28306
|
+
session: yield* openVaultSessionInteractive(api),
|
|
28307
|
+
credentialType: "push-certificate",
|
|
28308
|
+
downloaded: data
|
|
28309
|
+
});
|
|
28310
|
+
const p12Base64 = yield* secretString(secret, "p12Base64");
|
|
28311
|
+
const p12Password = yield* secretString(secret, "p12Password");
|
|
28312
|
+
const filePath = resolveOutputPath(cwd, output, `${data.id}.p12`);
|
|
28313
|
+
yield* writeBinary(filePath, fromBase64(p12Base64));
|
|
28314
|
+
return {
|
|
28315
|
+
path: filePath,
|
|
28316
|
+
pairs: [
|
|
28317
|
+
["Path", filePath],
|
|
28318
|
+
["Type", "Apple push SSL certificate (.p12)"],
|
|
28319
|
+
["Bundle", data.bundleIdentifier],
|
|
28320
|
+
["Serial", data.serialNumber],
|
|
28321
|
+
["Apple team", data.appleTeamIdentifier],
|
|
28322
|
+
["Valid from", data.validFrom],
|
|
28323
|
+
["Valid until", data.validUntil],
|
|
28324
|
+
["P12 password", p12Password]
|
|
28325
|
+
],
|
|
28326
|
+
metadata: {
|
|
28327
|
+
bundleIdentifier: data.bundleIdentifier,
|
|
28328
|
+
serialNumber: data.serialNumber,
|
|
28329
|
+
appleTeamIdentifier: data.appleTeamIdentifier,
|
|
28330
|
+
validFrom: data.validFrom,
|
|
28331
|
+
validUntil: data.validUntil,
|
|
28332
|
+
p12Password
|
|
28333
|
+
}
|
|
28334
|
+
};
|
|
28335
|
+
});
|
|
28336
|
+
const downloadPayCertificate = ({ api, id, cwd, output }) => Effect.gen(function* () {
|
|
28337
|
+
const data = yield* api.applePayCertificates.download({ path: { id } });
|
|
28338
|
+
const secret = yield* openFromDownload({
|
|
28339
|
+
session: yield* openVaultSessionInteractive(api),
|
|
28340
|
+
credentialType: "apple-pay-certificate",
|
|
28341
|
+
downloaded: data
|
|
28342
|
+
});
|
|
28343
|
+
const p12Base64 = yield* secretString(secret, "p12Base64");
|
|
28344
|
+
const p12Password = yield* secretString(secret, "p12Password");
|
|
28345
|
+
const filePath = resolveOutputPath(cwd, output, `${data.id}.p12`);
|
|
28346
|
+
yield* writeBinary(filePath, fromBase64(p12Base64));
|
|
28347
|
+
return {
|
|
28348
|
+
path: filePath,
|
|
28349
|
+
pairs: [
|
|
28350
|
+
["Path", filePath],
|
|
28351
|
+
["Type", "Apple Pay payment processing certificate (.p12)"],
|
|
28352
|
+
["Merchant", data.merchantIdentifier],
|
|
28353
|
+
["Serial", data.serialNumber],
|
|
28354
|
+
["Apple team", data.appleTeamIdentifier],
|
|
28355
|
+
["Valid from", data.validFrom],
|
|
28356
|
+
["Valid until", data.validUntil],
|
|
28357
|
+
["P12 password", p12Password]
|
|
28358
|
+
],
|
|
28359
|
+
metadata: {
|
|
28360
|
+
merchantIdentifier: data.merchantIdentifier,
|
|
28361
|
+
serialNumber: data.serialNumber,
|
|
28362
|
+
appleTeamIdentifier: data.appleTeamIdentifier,
|
|
28363
|
+
validFrom: data.validFrom,
|
|
28364
|
+
validUntil: data.validUntil,
|
|
28365
|
+
p12Password
|
|
28366
|
+
}
|
|
28367
|
+
};
|
|
28368
|
+
});
|
|
28369
|
+
const downloadPassTypeCertificate = ({ api, id, cwd, output }) => Effect.gen(function* () {
|
|
28370
|
+
const data = yield* api.applePassTypeCertificates.download({ path: { id } });
|
|
28371
|
+
const secret = yield* openFromDownload({
|
|
28372
|
+
session: yield* openVaultSessionInteractive(api),
|
|
28373
|
+
credentialType: "pass-type-certificate",
|
|
28374
|
+
downloaded: data
|
|
28375
|
+
});
|
|
28376
|
+
const p12Base64 = yield* secretString(secret, "p12Base64");
|
|
28377
|
+
const p12Password = yield* secretString(secret, "p12Password");
|
|
28378
|
+
const filePath = resolveOutputPath(cwd, output, `${data.id}.p12`);
|
|
28379
|
+
yield* writeBinary(filePath, fromBase64(p12Base64));
|
|
28380
|
+
return {
|
|
28381
|
+
path: filePath,
|
|
28382
|
+
pairs: [
|
|
28383
|
+
["Path", filePath],
|
|
28384
|
+
["Type", "Wallet Pass Type ID certificate (.p12)"],
|
|
28385
|
+
["Pass Type ID", data.passTypeIdentifier],
|
|
28386
|
+
["Serial", data.serialNumber],
|
|
28387
|
+
["Apple team", data.appleTeamIdentifier],
|
|
28388
|
+
["Valid from", data.validFrom],
|
|
28389
|
+
["Valid until", data.validUntil],
|
|
28390
|
+
["P12 password", p12Password]
|
|
28391
|
+
],
|
|
28392
|
+
metadata: {
|
|
28393
|
+
passTypeIdentifier: data.passTypeIdentifier,
|
|
28394
|
+
serialNumber: data.serialNumber,
|
|
28395
|
+
appleTeamIdentifier: data.appleTeamIdentifier,
|
|
28396
|
+
validFrom: data.validFrom,
|
|
28397
|
+
validUntil: data.validUntil,
|
|
28398
|
+
p12Password
|
|
28399
|
+
}
|
|
28400
|
+
};
|
|
28401
|
+
});
|
|
27858
28402
|
const downloadAscApiKey$1 = ({ api, id, cwd, output }) => Effect.gen(function* () {
|
|
27859
28403
|
const data = yield* api.ascApiKeys.getCredentials({ path: { id } });
|
|
27860
28404
|
const p8Pem = yield* secretString(yield* openFromDownload({
|
|
@@ -27939,6 +28483,9 @@ const dispatchDownload = (ctx, type) => {
|
|
|
27939
28483
|
case "distribution-certificate": return downloadDistributionCertificate(ctx);
|
|
27940
28484
|
case "provisioning-profile": return downloadProvisioningProfile$1(ctx);
|
|
27941
28485
|
case "push-key": return downloadPushKey(ctx);
|
|
28486
|
+
case "push-certificate": return downloadPushCertificate(ctx);
|
|
28487
|
+
case "apple-pay-certificate": return downloadPayCertificate(ctx);
|
|
28488
|
+
case "pass-type-certificate": return downloadPassTypeCertificate(ctx);
|
|
27942
28489
|
case "asc-api-key": return downloadAscApiKey$1(ctx);
|
|
27943
28490
|
case "keystore": return downloadKeystore(ctx);
|
|
27944
28491
|
case "google-service-account-key": return downloadGoogleServiceAccountKey(ctx);
|
|
@@ -27984,6 +28531,99 @@ const downloadCommand = defineCommand({
|
|
|
27984
28531
|
}), { json: "value" })
|
|
27985
28532
|
});
|
|
27986
28533
|
|
|
28534
|
+
//#endregion
|
|
28535
|
+
//#region src/lib/credentials-generator-merchant.ts
|
|
28536
|
+
/**
|
|
28537
|
+
* Enable the Apple Pay capability on an App ID, registering the App ID first if
|
|
28538
|
+
* it does not exist yet. Returns once the capability is on.
|
|
28539
|
+
*/
|
|
28540
|
+
const enableApplePayCapability = (ctx, bundleIdentifier) => Effect.gen(function* () {
|
|
28541
|
+
const bundle = (yield* wrap("apple-find-bundle-id", async () => AppleUtils.BundleId.findAsync(ctx, { identifier: bundleIdentifier }))) ?? (yield* wrap("apple-create-bundle-id", async () => AppleUtils.BundleId.createAsync(ctx, {
|
|
28542
|
+
identifier: bundleIdentifier,
|
|
28543
|
+
name: bundleIdentifier,
|
|
28544
|
+
platform: AppleUtils.BundleIdPlatform.IOS
|
|
28545
|
+
})));
|
|
28546
|
+
yield* wrap("apple-enable-apple-pay", async () => bundle.updateBundleIdCapabilityAsync({
|
|
28547
|
+
capabilityType: AppleUtils.CapabilityType.APPLE_PAY,
|
|
28548
|
+
option: AppleUtils.CapabilityTypeOption.ON
|
|
28549
|
+
}));
|
|
28550
|
+
});
|
|
28551
|
+
/**
|
|
28552
|
+
* Register an Apple Pay Merchant ID (`merchant.*`) on the Developer Portal via an
|
|
28553
|
+
* Apple ID session, optionally enabling the Apple Pay capability on an App ID.
|
|
28554
|
+
* This is the one piece of Apple Pay onboarding the portal API supports — the
|
|
28555
|
+
* payment-processing certificate itself is still created out-of-band (usually by
|
|
28556
|
+
* the PSP) and uploaded via `credentials upload --type apple-pay-certificate`.
|
|
28557
|
+
*/
|
|
28558
|
+
const registerMerchantIdViaAppleId = (input) => Effect.gen(function* () {
|
|
28559
|
+
const merchant = yield* wrap("apple-create-merchant-id", async () => AppleUtils.MerchantId.createAsync(input.context, {
|
|
28560
|
+
identifier: input.identifier,
|
|
28561
|
+
name: input.name
|
|
28562
|
+
}));
|
|
28563
|
+
if (input.bundleIdentifier !== void 0 && input.bundleIdentifier.length > 0) yield* enableApplePayCapability(input.context, input.bundleIdentifier);
|
|
28564
|
+
return {
|
|
28565
|
+
developerPortalIdentifier: merchant.id,
|
|
28566
|
+
identifier: input.identifier,
|
|
28567
|
+
name: input.name,
|
|
28568
|
+
capabilityEnabledForBundleId: input.bundleIdentifier
|
|
28569
|
+
};
|
|
28570
|
+
});
|
|
28571
|
+
|
|
28572
|
+
//#endregion
|
|
28573
|
+
//#region src/commands/credentials/generate-merchant-id.ts
|
|
28574
|
+
const MERCHANT_ID_PATTERN = /^merchant\.[A-Za-z0-9][A-Za-z0-9.-]*$/u;
|
|
28575
|
+
const MERCHANT_EXIT_EXTRAS = {
|
|
28576
|
+
CredentialValidationError: 2,
|
|
28577
|
+
AppleIdGenerateFailedError: 6,
|
|
28578
|
+
AppleAuthError: 4,
|
|
28579
|
+
InteractiveProhibitedError: 4
|
|
28580
|
+
};
|
|
28581
|
+
const merchantIdCommand = defineCommand({
|
|
28582
|
+
meta: {
|
|
28583
|
+
name: "merchant-id",
|
|
28584
|
+
description: "Register an Apple Pay Merchant ID (merchant.*) on the Developer Portal via Apple ID login, optionally turning on Apple Pay for an App ID. The payment-processing certificate itself is uploaded separately with `credentials upload --type apple-pay-certificate`."
|
|
28585
|
+
},
|
|
28586
|
+
args: {
|
|
28587
|
+
identifier: {
|
|
28588
|
+
type: "string",
|
|
28589
|
+
required: true,
|
|
28590
|
+
description: "Merchant ID (merchant.*)"
|
|
28591
|
+
},
|
|
28592
|
+
name: {
|
|
28593
|
+
type: "string",
|
|
28594
|
+
description: "Display name (defaults to the identifier)"
|
|
28595
|
+
},
|
|
28596
|
+
"bundle-identifier": {
|
|
28597
|
+
type: "string",
|
|
28598
|
+
description: "App ID to enable the Apple Pay capability on"
|
|
28599
|
+
}
|
|
28600
|
+
},
|
|
28601
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
28602
|
+
const identifier = args.identifier.trim();
|
|
28603
|
+
if (!MERCHANT_ID_PATTERN.test(identifier)) return yield* new CredentialValidationError({ message: `Merchant ID "${identifier}" must look like merchant.com.example.` });
|
|
28604
|
+
const auth = yield* AppleAuth;
|
|
28605
|
+
const session = yield* auth.ensureLoggedIn();
|
|
28606
|
+
yield* printHuman("Registering Apple Pay Merchant ID via your Apple ID...");
|
|
28607
|
+
const created = yield* registerMerchantIdViaAppleId({
|
|
28608
|
+
context: auth.buildRequestContext(session),
|
|
28609
|
+
identifier,
|
|
28610
|
+
name: args.name ?? identifier,
|
|
28611
|
+
...compact({ bundleIdentifier: args["bundle-identifier"] })
|
|
28612
|
+
});
|
|
28613
|
+
yield* printHuman("Merchant ID registered.");
|
|
28614
|
+
yield* printHumanKeyValue([
|
|
28615
|
+
["Merchant ID", created.identifier],
|
|
28616
|
+
["Name", created.name],
|
|
28617
|
+
["Apple identifier", created.developerPortalIdentifier],
|
|
28618
|
+
["Apple Pay enabled on", created.capabilityEnabledForBundleId ?? "-"]
|
|
28619
|
+
]);
|
|
28620
|
+
return created;
|
|
28621
|
+
}), {
|
|
28622
|
+
exits: MERCHANT_EXIT_EXTRAS,
|
|
28623
|
+
json: "value"
|
|
28624
|
+
})
|
|
28625
|
+
});
|
|
28626
|
+
|
|
27987
28627
|
//#endregion
|
|
27988
28628
|
//#region src/commands/credentials/generate-push-key.ts
|
|
27989
28629
|
const PUSH_KEY_EXIT_EXTRAS = {
|
|
@@ -28382,6 +29022,7 @@ const generateCommand$1 = defineCommand({
|
|
|
28382
29022
|
"distribution-certificate": distributionCertificateCommand$1,
|
|
28383
29023
|
"provisioning-profile": provisioningProfileCommand,
|
|
28384
29024
|
"push-key": pushKeyCommand$1,
|
|
29025
|
+
"merchant-id": merchantIdCommand,
|
|
28385
29026
|
"gsa-key": gsaKeyCommand
|
|
28386
29027
|
}
|
|
28387
29028
|
});
|
|
@@ -28682,6 +29323,9 @@ const CREDENTIAL_TYPES$2 = [
|
|
|
28682
29323
|
"distribution-certificate",
|
|
28683
29324
|
"provisioning-profile",
|
|
28684
29325
|
"push-key",
|
|
29326
|
+
"push-certificate",
|
|
29327
|
+
"apple-pay-certificate",
|
|
29328
|
+
"pass-type-certificate",
|
|
28685
29329
|
"asc-api-key",
|
|
28686
29330
|
"keystore",
|
|
28687
29331
|
"google-service-account-key"
|
|
@@ -28856,10 +29500,8 @@ const resolvePushKeyTarget = (api, idArg) => Effect.gen(function* () {
|
|
|
28856
29500
|
const [only] = items;
|
|
28857
29501
|
if (only !== void 0) return only;
|
|
28858
29502
|
}
|
|
28859
|
-
const
|
|
28860
|
-
|
|
28861
|
-
label: `${entry.keyId} (team ${entry.appleTeamId})`
|
|
28862
|
-
})));
|
|
29503
|
+
const teamLabel = makeAppleTeamLabeler((yield* api.appleTeams.list()).items);
|
|
29504
|
+
const chosen = yield* promptSelect("Select a push key to revoke", items.map((key) => pushKeyChoice(key, teamLabel(key.appleTeamId))));
|
|
28863
29505
|
const match = items.find((entry) => entry.id === chosen);
|
|
28864
29506
|
if (match === void 0) return yield* new CredentialValidationError({ message: `Selected push key ${chosen} not found after listing.` });
|
|
28865
29507
|
return match;
|
|
@@ -29548,6 +30190,9 @@ const CREDENTIAL_TYPES$1 = [
|
|
|
29548
30190
|
"distribution-certificate",
|
|
29549
30191
|
"provisioning-profile",
|
|
29550
30192
|
"push-key",
|
|
30193
|
+
"push-certificate",
|
|
30194
|
+
"apple-pay-certificate",
|
|
30195
|
+
"pass-type-certificate",
|
|
29551
30196
|
"asc-api-key",
|
|
29552
30197
|
"keystore",
|
|
29553
30198
|
"google-service-account-key"
|
|
@@ -29601,6 +30246,18 @@ const uploadCommand = defineCommand({
|
|
|
29601
30246
|
"apple-team-identifier": {
|
|
29602
30247
|
type: "string",
|
|
29603
30248
|
description: "Apple Team ID"
|
|
30249
|
+
},
|
|
30250
|
+
"bundle-identifier": {
|
|
30251
|
+
type: "string",
|
|
30252
|
+
description: "App ID for a push certificate (else derived from the cert CN)"
|
|
30253
|
+
},
|
|
30254
|
+
"merchant-identifier": {
|
|
30255
|
+
type: "string",
|
|
30256
|
+
description: "Merchant ID (merchant.*) for an Apple Pay certificate"
|
|
30257
|
+
},
|
|
30258
|
+
"pass-type-identifier": {
|
|
30259
|
+
type: "string",
|
|
30260
|
+
description: "Pass Type ID (pass.*) for a Pass Type ID certificate"
|
|
29604
30261
|
}
|
|
29605
30262
|
},
|
|
29606
30263
|
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
@@ -29615,7 +30272,10 @@ const uploadCommand = defineCommand({
|
|
|
29615
30272
|
keyPassword: args["key-password"],
|
|
29616
30273
|
keyId: args["key-id"],
|
|
29617
30274
|
issuerId: args["issuer-id"],
|
|
29618
|
-
appleTeamIdentifier: args["apple-team-identifier"]
|
|
30275
|
+
appleTeamIdentifier: args["apple-team-identifier"],
|
|
30276
|
+
bundleIdentifier: args["bundle-identifier"],
|
|
30277
|
+
merchantIdentifier: args["merchant-identifier"],
|
|
30278
|
+
passTypeIdentifier: args["pass-type-identifier"]
|
|
29619
30279
|
})
|
|
29620
30280
|
});
|
|
29621
30281
|
yield* printHuman("Credential uploaded successfully.");
|
|
@@ -29689,6 +30349,9 @@ const CREDENTIAL_TYPES = [
|
|
|
29689
30349
|
"distribution-certificate",
|
|
29690
30350
|
"provisioning-profile",
|
|
29691
30351
|
"push-key",
|
|
30352
|
+
"push-certificate",
|
|
30353
|
+
"apple-pay-certificate",
|
|
30354
|
+
"pass-type-certificate",
|
|
29692
30355
|
"asc-api-key",
|
|
29693
30356
|
"keystore",
|
|
29694
30357
|
"google-service-account-key"
|
|
@@ -29753,6 +30416,66 @@ const viewPushKey = (api, id) => Effect.gen(function* () {
|
|
|
29753
30416
|
raw: item
|
|
29754
30417
|
};
|
|
29755
30418
|
});
|
|
30419
|
+
const viewPushCertificate = (api, id) => Effect.gen(function* () {
|
|
30420
|
+
const { items } = yield* api.applePushCertificates.list();
|
|
30421
|
+
const item = items.find((entry) => entry.id === id);
|
|
30422
|
+
if (!item) return yield* notFound(id, "push-certificate");
|
|
30423
|
+
return {
|
|
30424
|
+
kind: "push-certificate",
|
|
30425
|
+
pairs: [
|
|
30426
|
+
["ID", item.id],
|
|
30427
|
+
["Type", "Apple push SSL certificate"],
|
|
30428
|
+
["Bundle identifier", item.bundleIdentifier],
|
|
30429
|
+
["Serial number", item.serialNumber],
|
|
30430
|
+
["Apple team ID", item.appleTeamId],
|
|
30431
|
+
["Valid from", item.validFrom],
|
|
30432
|
+
["Valid until", item.validUntil],
|
|
30433
|
+
["Created", item.createdAt],
|
|
30434
|
+
["Updated", item.updatedAt]
|
|
30435
|
+
],
|
|
30436
|
+
raw: item
|
|
30437
|
+
};
|
|
30438
|
+
});
|
|
30439
|
+
const viewPayCertificate = (api, id) => Effect.gen(function* () {
|
|
30440
|
+
const { items } = yield* api.applePayCertificates.list();
|
|
30441
|
+
const item = items.find((entry) => entry.id === id);
|
|
30442
|
+
if (!item) return yield* notFound(id, "apple-pay-certificate");
|
|
30443
|
+
return {
|
|
30444
|
+
kind: "apple-pay-certificate",
|
|
30445
|
+
pairs: [
|
|
30446
|
+
["ID", item.id],
|
|
30447
|
+
["Type", "Apple Pay payment processing certificate"],
|
|
30448
|
+
["Merchant identifier", item.merchantIdentifier],
|
|
30449
|
+
["Serial number", item.serialNumber],
|
|
30450
|
+
["Apple team ID", item.appleTeamId],
|
|
30451
|
+
["Valid from", item.validFrom],
|
|
30452
|
+
["Valid until", item.validUntil],
|
|
30453
|
+
["Created", item.createdAt],
|
|
30454
|
+
["Updated", item.updatedAt]
|
|
30455
|
+
],
|
|
30456
|
+
raw: item
|
|
30457
|
+
};
|
|
30458
|
+
});
|
|
30459
|
+
const viewPassTypeCertificate = (api, id) => Effect.gen(function* () {
|
|
30460
|
+
const { items } = yield* api.applePassTypeCertificates.list();
|
|
30461
|
+
const item = items.find((entry) => entry.id === id);
|
|
30462
|
+
if (!item) return yield* notFound(id, "pass-type-certificate");
|
|
30463
|
+
return {
|
|
30464
|
+
kind: "pass-type-certificate",
|
|
30465
|
+
pairs: [
|
|
30466
|
+
["ID", item.id],
|
|
30467
|
+
["Type", "Wallet Pass Type ID certificate"],
|
|
30468
|
+
["Pass Type ID", item.passTypeIdentifier],
|
|
30469
|
+
["Serial number", item.serialNumber],
|
|
30470
|
+
["Apple team ID", item.appleTeamId],
|
|
30471
|
+
["Valid from", item.validFrom],
|
|
30472
|
+
["Valid until", item.validUntil],
|
|
30473
|
+
["Created", item.createdAt],
|
|
30474
|
+
["Updated", item.updatedAt]
|
|
30475
|
+
],
|
|
30476
|
+
raw: item
|
|
30477
|
+
};
|
|
30478
|
+
});
|
|
29756
30479
|
const viewAscApiKey = (api, id) => Effect.gen(function* () {
|
|
29757
30480
|
const { items } = yield* api.ascApiKeys.list();
|
|
29758
30481
|
const item = items.find((entry) => entry.id === id);
|
|
@@ -29811,6 +30534,9 @@ const lookupByType = (api, id, type) => {
|
|
|
29811
30534
|
case "distribution-certificate": return viewDistributionCertificate(api, id);
|
|
29812
30535
|
case "provisioning-profile": return viewProvisioningProfile(api, id);
|
|
29813
30536
|
case "push-key": return viewPushKey(api, id);
|
|
30537
|
+
case "push-certificate": return viewPushCertificate(api, id);
|
|
30538
|
+
case "apple-pay-certificate": return viewPayCertificate(api, id);
|
|
30539
|
+
case "pass-type-certificate": return viewPassTypeCertificate(api, id);
|
|
29814
30540
|
case "asc-api-key": return viewAscApiKey(api, id);
|
|
29815
30541
|
case "keystore": return viewKeystore(api, id);
|
|
29816
30542
|
case "google-service-account-key": return viewGoogleServiceAccountKey(api, id);
|
|
@@ -31999,6 +32725,20 @@ const persistLink = (projectRoot, projectId, hasExpoConfig) => Effect.gen(functi
|
|
|
31999
32725
|
configPath: filePath
|
|
32000
32726
|
};
|
|
32001
32727
|
});
|
|
32728
|
+
/**
|
|
32729
|
+
* Scaffold the default `eas.json` build profiles after linking so a freshly
|
|
32730
|
+
* `init`ed project can `build` straight away. Only acts when no `build` section
|
|
32731
|
+
* exists yet — a project that already defines profiles is left untouched (run
|
|
32732
|
+
* `build configure` to top those up). Use `build configure` to re-scaffold.
|
|
32733
|
+
*/
|
|
32734
|
+
const scaffoldBuildProfiles = (projectRoot) => Effect.gen(function* () {
|
|
32735
|
+
const existing = yield* readEasJsonRaw(projectRoot);
|
|
32736
|
+
const existingBuild = isRecord$1(existing?.["build"]) ? existing["build"] : {};
|
|
32737
|
+
if (Object.keys(existingBuild).length > 0) return [];
|
|
32738
|
+
const result = yield* ensureDefaultBuildProfiles(projectRoot);
|
|
32739
|
+
yield* printHuman(`Scaffolded eas.json with default build profiles: ${result.added.join(", ")}.`);
|
|
32740
|
+
return result.added;
|
|
32741
|
+
});
|
|
32002
32742
|
const initCommand = defineCommand({
|
|
32003
32743
|
meta: {
|
|
32004
32744
|
name: "init",
|
|
@@ -32026,9 +32766,12 @@ const initCommand = defineCommand({
|
|
|
32026
32766
|
if (args.id !== void 0 && args.id.length > 0) {
|
|
32027
32767
|
const project = yield* api.projects.get({ path: { id: args.id } });
|
|
32028
32768
|
yield* printHuman(`Linking project: ${project.name} (${project.id})`);
|
|
32769
|
+
const linked = yield* persistLink(projectRoot, project.id, hasExpoConfig);
|
|
32770
|
+
const buildProfiles = yield* scaffoldBuildProfiles(projectRoot);
|
|
32029
32771
|
return {
|
|
32030
32772
|
linked: true,
|
|
32031
|
-
...
|
|
32773
|
+
...linked,
|
|
32774
|
+
buildProfiles
|
|
32032
32775
|
};
|
|
32033
32776
|
}
|
|
32034
32777
|
const { name, slug } = yield* resolveNameAndSlug(args, projectRoot, expoConfig);
|
|
@@ -32042,21 +32785,24 @@ const initCommand = defineCommand({
|
|
|
32042
32785
|
limit: 100
|
|
32043
32786
|
} });
|
|
32044
32787
|
const existing = items.find((project) => project.slug === slug);
|
|
32788
|
+
const linked = yield* persistLink(projectRoot, yield* Effect.gen(function* () {
|
|
32789
|
+
if (existing) {
|
|
32790
|
+
yield* printHuman(`Found existing project: ${existing.name} (${existing.id})`);
|
|
32791
|
+
return existing.id;
|
|
32792
|
+
}
|
|
32793
|
+
yield* printHuman("No existing project found. Creating new project...");
|
|
32794
|
+
const created = yield* api.projects.create({ payload: {
|
|
32795
|
+
name,
|
|
32796
|
+
slug
|
|
32797
|
+
} });
|
|
32798
|
+
yield* printHuman(`Created project: ${created.name} (${created.id})`);
|
|
32799
|
+
return created.id;
|
|
32800
|
+
}), hasExpoConfig);
|
|
32801
|
+
const buildProfiles = yield* scaffoldBuildProfiles(projectRoot);
|
|
32045
32802
|
return {
|
|
32046
32803
|
linked: true,
|
|
32047
|
-
...
|
|
32048
|
-
|
|
32049
|
-
yield* printHuman(`Found existing project: ${existing.name} (${existing.id})`);
|
|
32050
|
-
return existing.id;
|
|
32051
|
-
}
|
|
32052
|
-
yield* printHuman("No existing project found. Creating new project...");
|
|
32053
|
-
const created = yield* api.projects.create({ payload: {
|
|
32054
|
-
name,
|
|
32055
|
-
slug
|
|
32056
|
-
} });
|
|
32057
|
-
yield* printHuman(`Created project: ${created.name} (${created.id})`);
|
|
32058
|
-
return created.id;
|
|
32059
|
-
}), hasExpoConfig)
|
|
32804
|
+
...linked,
|
|
32805
|
+
buildProfiles
|
|
32060
32806
|
};
|
|
32061
32807
|
}), { json: "value" })
|
|
32062
32808
|
});
|
|
@@ -35873,10 +36619,11 @@ const bootstrapVersionCheck = (currentVersion, installerHint, spawnRefresh, opti
|
|
|
35873
36619
|
if (yield* isOptedOut) return;
|
|
35874
36620
|
const versionCheck = yield* VersionCheck;
|
|
35875
36621
|
if (options?.quiet !== true) {
|
|
35876
|
-
|
|
35877
|
-
if (
|
|
36622
|
+
let latest = yield* versionCheck.cachedLatest;
|
|
36623
|
+
if (latest === void 0) latest = yield* versionCheck.fetchLatest;
|
|
36624
|
+
if (latest && isNewerVersion(latest, currentVersion)) {
|
|
35878
36625
|
const installer = detectInstallerFromImportMetaUrl(installerHint);
|
|
35879
|
-
yield* Console.error(formatNotice(currentVersion,
|
|
36626
|
+
yield* Console.error(formatNotice(currentVersion, latest, installCommand(installer)));
|
|
35880
36627
|
}
|
|
35881
36628
|
}
|
|
35882
36629
|
if (yield* versionCheck.cacheStale) spawnRefresh();
|