@better-update/cli 0.37.0 → 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 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.37.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. Five signing-credential tables plus `envVarValue` (one
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 = 1440 * 60 * 1e3;
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
- refreshCache: Effect.gen(function* () {
3599
- const request = HttpClientRequest.get(NPM_REGISTRY_URL).pipe(HttpClientRequest.setHeader("accept", "application/json"));
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
  */
@@ -24210,34 +24474,6 @@ const runBuildWorkflow = (options) => Effect.scoped(Effect.gen(function* () {
24210
24474
 
24211
24475
  //#endregion
24212
24476
  //#region src/commands/build/configure.ts
24213
- const DEFAULT_EAS_JSON = {
24214
- cli: { version: ">= 7.0.0" },
24215
- build: {
24216
- development: {
24217
- developmentClient: true,
24218
- distribution: "internal",
24219
- channel: "development",
24220
- environment: "development",
24221
- android: { format: "apk" }
24222
- },
24223
- preview: {
24224
- distribution: "internal",
24225
- channel: "preview",
24226
- environment: "preview",
24227
- android: { format: "apk" }
24228
- },
24229
- production: {
24230
- channel: "production",
24231
- environment: "production",
24232
- android: { format: "aab" }
24233
- }
24234
- }
24235
- };
24236
- const DEFAULT_PROFILES = [
24237
- "development",
24238
- "preview",
24239
- "production"
24240
- ];
24241
24477
  const writeEasJson = (filePath, value) => Effect.gen(function* () {
24242
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}` })));
24243
24479
  });
@@ -24253,43 +24489,44 @@ const configureBuildCommand = defineCommand({
24253
24489
  run: async ({ args }) => runEffect(Effect.gen(function* () {
24254
24490
  const { allow: interactive } = yield* InteractiveMode;
24255
24491
  const projectRoot = yield* (yield* CliRuntime).cwd;
24256
- const easJsonPath = path.join(projectRoot, "eas.json");
24492
+ const filePath = easJsonPath(projectRoot);
24257
24493
  const fs = yield* FileSystem.FileSystem;
24258
- if (!(yield* fs.exists(easJsonPath))) {
24259
- yield* writeEasJson(easJsonPath, DEFAULT_EAS_JSON);
24260
- yield* printHuman(`Wrote eas.json with default profiles to ${easJsonPath}.`);
24261
- yield* printHumanKeyValue([["Profiles", DEFAULT_PROFILES.join(", ")], ["Path", easJsonPath]]);
24262
- return {
24263
- action: "created",
24264
- path: easJsonPath,
24265
- profiles: [...DEFAULT_PROFILES]
24266
- };
24267
- }
24494
+ const exists = yield* fs.exists(filePath);
24268
24495
  if (args.force === true) {
24269
- if (!(interactive ? yield* promptConfirm(`Overwrite existing eas.json at ${easJsonPath} with defaults?`) : true)) {
24496
+ if (!(exists && interactive ? yield* promptConfirm(`Overwrite existing eas.json at ${filePath} with defaults?`) : true)) {
24270
24497
  yield* printHuman("Aborted. eas.json was not modified.");
24271
24498
  return {
24272
24499
  action: "aborted",
24273
- path: easJsonPath
24500
+ path: filePath
24274
24501
  };
24275
24502
  }
24276
- yield* writeEasJson(easJsonPath, DEFAULT_EAS_JSON);
24277
- yield* printHuman(`Overwrote eas.json with default profiles.`);
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}.`);
24505
+ return {
24506
+ action: exists ? "overwritten" : "created",
24507
+ path: filePath,
24508
+ profiles: [...DEFAULT_PROFILE_NAMES]
24509
+ };
24510
+ }
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]]);
24278
24515
  return {
24279
- action: "overwritten",
24280
- path: easJsonPath,
24281
- profiles: [...DEFAULT_PROFILES]
24516
+ action: "created",
24517
+ path: created.path,
24518
+ profiles: created.added
24282
24519
  };
24283
24520
  }
24284
- const config = yield* parseEasConfig(yield* fs.readFileString(easJsonPath).pipe(Effect.mapError((cause) => new BuildProfileError({ message: `Failed to read eas.json: ${cause.message}` }))));
24521
+ const config = yield* parseEasConfig(yield* fs.readFileString(filePath).pipe(Effect.mapError((cause) => new BuildProfileError({ message: `Failed to read eas.json: ${cause.message}` }))));
24285
24522
  const existingProfiles = Object.keys(config.build ?? {});
24286
- const missing = DEFAULT_PROFILES.filter((name) => !existingProfiles.includes(name));
24523
+ const missing = DEFAULT_PROFILE_NAMES.filter((name) => !existingProfiles.includes(name));
24287
24524
  if (missing.length === 0) {
24288
24525
  yield* printHuman(`eas.json already defines all default profiles (${existingProfiles.join(", ")}). Nothing to add.`);
24289
24526
  yield* printHuman("Pass --force to overwrite with the default template.");
24290
24527
  return {
24291
24528
  action: "noop",
24292
- path: easJsonPath,
24529
+ path: filePath,
24293
24530
  existing: existingProfiles
24294
24531
  };
24295
24532
  }
@@ -24297,28 +24534,21 @@ const configureBuildCommand = defineCommand({
24297
24534
  yield* printHuman("Aborted. eas.json was not modified.");
24298
24535
  return {
24299
24536
  action: "aborted",
24300
- path: easJsonPath
24537
+ path: filePath
24301
24538
  };
24302
24539
  }
24303
- const additions = Object.fromEntries(missing.map((name) => [name, DEFAULT_EAS_JSON.build[name]]));
24304
- yield* writeEasJson(easJsonPath, {
24305
- build: {
24306
- ...config.build,
24307
- ...additions
24308
- },
24309
- ...compact({ cli: config.cli })
24310
- });
24311
- 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(", ")}.`);
24312
24542
  yield* printHumanKeyValue([
24313
24543
  ["Existing", existingProfiles.join(", ") || "(none)"],
24314
- ["Added", missing.join(", ")],
24315
- ["Path", easJsonPath]
24544
+ ["Added", result.added.join(", ")],
24545
+ ["Path", result.path]
24316
24546
  ]);
24317
24547
  return {
24318
24548
  action: "topped-up",
24319
- path: easJsonPath,
24549
+ path: result.path,
24320
24550
  existing: existingProfiles,
24321
- added: [...missing]
24551
+ added: result.added
24322
24552
  };
24323
24553
  }), { json: "value" })
24324
24554
  });
@@ -25858,13 +26088,160 @@ const inspectP12 = (params) => Effect.try({
25858
26088
  catch: (error) => new CredentialValidationError({ message: `Failed to parse P12 certificate: ${error instanceof Error ? error.message : String(error)}` })
25859
26089
  });
25860
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
+
25861
26235
  //#endregion
25862
26236
  //#region src/lib/credentials-manager.ts
25863
26237
  const formatDistribution = (value) => value.toLowerCase().replaceAll("_", "-");
25864
26238
  const listAllCredentials = (api) => Effect.gen(function* () {
25865
- const [certs, pushKeys, ascKeys, profiles, keystores, googleKeys] = yield* Effect.all([
26239
+ const [certs, pushKeys, pushCerts, payCerts, passCerts, ascKeys, profiles, keystores, googleKeys] = yield* Effect.all([
25866
26240
  api.appleDistributionCertificates.list(),
25867
26241
  api.applePushKeys.list(),
26242
+ api.applePushCertificates.list(),
26243
+ api.applePayCertificates.list(),
26244
+ api.applePassTypeCertificates.list(),
25868
26245
  api.ascApiKeys.list(),
25869
26246
  api.appleProvisioningProfiles.list({ urlParams: {} }),
25870
26247
  api.androidUploadKeystores.list(),
@@ -25885,6 +26262,27 @@ const listAllCredentials = (api) => Effect.gen(function* () {
25885
26262
  type: "push-key",
25886
26263
  distribution: null
25887
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
+ })),
25888
26286
  ...ascKeys.items.map((key) => ({
25889
26287
  id: key.id,
25890
26288
  name: key.name,
@@ -26082,6 +26480,9 @@ const uploadAndroidGoogleServiceAccountKey = (api, input, bytes) => Effect.gen(f
26082
26480
  const uploadHandlers = {
26083
26481
  "ios:distribution-certificate": uploadIosDistributionCertificate,
26084
26482
  "ios:push-key": uploadIosPushKey,
26483
+ "ios:push-certificate": uploadIosPushCertificate,
26484
+ "ios:apple-pay-certificate": uploadIosPayCertificate,
26485
+ "ios:pass-type-certificate": uploadIosPassTypeCertificate,
26085
26486
  "ios:asc-api-key": uploadIosAscApiKey,
26086
26487
  "ios:provisioning-profile": uploadIosProvisioningProfile,
26087
26488
  "android:keystore": uploadAndroidKeystore,
@@ -26107,6 +26508,15 @@ const deleteCredential = (api, input) => {
26107
26508
  platform: "ios",
26108
26509
  type: "push-key"
26109
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({
26110
26520
  platform: "ios",
26111
26521
  type: "asc-api-key"
26112
26522
  }, () => api.ascApiKeys.delete({ path })), Match.when({
@@ -26187,6 +26597,9 @@ const TYPE_LABELS = {
26187
26597
  "distribution-certificate": "iOS distribution certificate",
26188
26598
  "provisioning-profile": "iOS provisioning profile",
26189
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",
26190
26603
  "asc-api-key": "ASC API key",
26191
26604
  keystore: "Android keystore",
26192
26605
  "google-service-account-key": "Google service account key"
@@ -27674,6 +28087,9 @@ const CREDENTIAL_TYPES$3 = [
27674
28087
  "distribution-certificate",
27675
28088
  "provisioning-profile",
27676
28089
  "push-key",
28090
+ "push-certificate",
28091
+ "apple-pay-certificate",
28092
+ "pass-type-certificate",
27677
28093
  "asc-api-key",
27678
28094
  "keystore",
27679
28095
  "google-service-account-key"
@@ -27790,6 +28206,9 @@ const DOWNLOAD_TYPES = [
27790
28206
  "distribution-certificate",
27791
28207
  "provisioning-profile",
27792
28208
  "push-key",
28209
+ "push-certificate",
28210
+ "apple-pay-certificate",
28211
+ "pass-type-certificate",
27793
28212
  "asc-api-key",
27794
28213
  "keystore",
27795
28214
  "google-service-account-key"
@@ -27881,6 +28300,105 @@ const downloadPushKey = ({ api, id, cwd, output }) => Effect.gen(function* () {
27881
28300
  }
27882
28301
  };
27883
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
+ });
27884
28402
  const downloadAscApiKey$1 = ({ api, id, cwd, output }) => Effect.gen(function* () {
27885
28403
  const data = yield* api.ascApiKeys.getCredentials({ path: { id } });
27886
28404
  const p8Pem = yield* secretString(yield* openFromDownload({
@@ -27965,6 +28483,9 @@ const dispatchDownload = (ctx, type) => {
27965
28483
  case "distribution-certificate": return downloadDistributionCertificate(ctx);
27966
28484
  case "provisioning-profile": return downloadProvisioningProfile$1(ctx);
27967
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);
27968
28489
  case "asc-api-key": return downloadAscApiKey$1(ctx);
27969
28490
  case "keystore": return downloadKeystore(ctx);
27970
28491
  case "google-service-account-key": return downloadGoogleServiceAccountKey(ctx);
@@ -28010,6 +28531,99 @@ const downloadCommand = defineCommand({
28010
28531
  }), { json: "value" })
28011
28532
  });
28012
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
+
28013
28627
  //#endregion
28014
28628
  //#region src/commands/credentials/generate-push-key.ts
28015
28629
  const PUSH_KEY_EXIT_EXTRAS = {
@@ -28408,6 +29022,7 @@ const generateCommand$1 = defineCommand({
28408
29022
  "distribution-certificate": distributionCertificateCommand$1,
28409
29023
  "provisioning-profile": provisioningProfileCommand,
28410
29024
  "push-key": pushKeyCommand$1,
29025
+ "merchant-id": merchantIdCommand,
28411
29026
  "gsa-key": gsaKeyCommand
28412
29027
  }
28413
29028
  });
@@ -28708,6 +29323,9 @@ const CREDENTIAL_TYPES$2 = [
28708
29323
  "distribution-certificate",
28709
29324
  "provisioning-profile",
28710
29325
  "push-key",
29326
+ "push-certificate",
29327
+ "apple-pay-certificate",
29328
+ "pass-type-certificate",
28711
29329
  "asc-api-key",
28712
29330
  "keystore",
28713
29331
  "google-service-account-key"
@@ -29572,6 +30190,9 @@ const CREDENTIAL_TYPES$1 = [
29572
30190
  "distribution-certificate",
29573
30191
  "provisioning-profile",
29574
30192
  "push-key",
30193
+ "push-certificate",
30194
+ "apple-pay-certificate",
30195
+ "pass-type-certificate",
29575
30196
  "asc-api-key",
29576
30197
  "keystore",
29577
30198
  "google-service-account-key"
@@ -29625,6 +30246,18 @@ const uploadCommand = defineCommand({
29625
30246
  "apple-team-identifier": {
29626
30247
  type: "string",
29627
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"
29628
30261
  }
29629
30262
  },
29630
30263
  run: async ({ args }) => runEffect(Effect.gen(function* () {
@@ -29639,7 +30272,10 @@ const uploadCommand = defineCommand({
29639
30272
  keyPassword: args["key-password"],
29640
30273
  keyId: args["key-id"],
29641
30274
  issuerId: args["issuer-id"],
29642
- 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"]
29643
30279
  })
29644
30280
  });
29645
30281
  yield* printHuman("Credential uploaded successfully.");
@@ -29713,6 +30349,9 @@ const CREDENTIAL_TYPES = [
29713
30349
  "distribution-certificate",
29714
30350
  "provisioning-profile",
29715
30351
  "push-key",
30352
+ "push-certificate",
30353
+ "apple-pay-certificate",
30354
+ "pass-type-certificate",
29716
30355
  "asc-api-key",
29717
30356
  "keystore",
29718
30357
  "google-service-account-key"
@@ -29777,6 +30416,66 @@ const viewPushKey = (api, id) => Effect.gen(function* () {
29777
30416
  raw: item
29778
30417
  };
29779
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
+ });
29780
30479
  const viewAscApiKey = (api, id) => Effect.gen(function* () {
29781
30480
  const { items } = yield* api.ascApiKeys.list();
29782
30481
  const item = items.find((entry) => entry.id === id);
@@ -29835,6 +30534,9 @@ const lookupByType = (api, id, type) => {
29835
30534
  case "distribution-certificate": return viewDistributionCertificate(api, id);
29836
30535
  case "provisioning-profile": return viewProvisioningProfile(api, id);
29837
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);
29838
30540
  case "asc-api-key": return viewAscApiKey(api, id);
29839
30541
  case "keystore": return viewKeystore(api, id);
29840
30542
  case "google-service-account-key": return viewGoogleServiceAccountKey(api, id);
@@ -32023,6 +32725,20 @@ const persistLink = (projectRoot, projectId, hasExpoConfig) => Effect.gen(functi
32023
32725
  configPath: filePath
32024
32726
  };
32025
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
+ });
32026
32742
  const initCommand = defineCommand({
32027
32743
  meta: {
32028
32744
  name: "init",
@@ -32050,9 +32766,12 @@ const initCommand = defineCommand({
32050
32766
  if (args.id !== void 0 && args.id.length > 0) {
32051
32767
  const project = yield* api.projects.get({ path: { id: args.id } });
32052
32768
  yield* printHuman(`Linking project: ${project.name} (${project.id})`);
32769
+ const linked = yield* persistLink(projectRoot, project.id, hasExpoConfig);
32770
+ const buildProfiles = yield* scaffoldBuildProfiles(projectRoot);
32053
32771
  return {
32054
32772
  linked: true,
32055
- ...yield* persistLink(projectRoot, project.id, hasExpoConfig)
32773
+ ...linked,
32774
+ buildProfiles
32056
32775
  };
32057
32776
  }
32058
32777
  const { name, slug } = yield* resolveNameAndSlug(args, projectRoot, expoConfig);
@@ -32066,21 +32785,24 @@ const initCommand = defineCommand({
32066
32785
  limit: 100
32067
32786
  } });
32068
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);
32069
32802
  return {
32070
32803
  linked: true,
32071
- ...yield* persistLink(projectRoot, yield* Effect.gen(function* () {
32072
- if (existing) {
32073
- yield* printHuman(`Found existing project: ${existing.name} (${existing.id})`);
32074
- return existing.id;
32075
- }
32076
- yield* printHuman("No existing project found. Creating new project...");
32077
- const created = yield* api.projects.create({ payload: {
32078
- name,
32079
- slug
32080
- } });
32081
- yield* printHuman(`Created project: ${created.name} (${created.id})`);
32082
- return created.id;
32083
- }), hasExpoConfig)
32804
+ ...linked,
32805
+ buildProfiles
32084
32806
  };
32085
32807
  }), { json: "value" })
32086
32808
  });
@@ -35897,10 +36619,11 @@ const bootstrapVersionCheck = (currentVersion, installerHint, spawnRefresh, opti
35897
36619
  if (yield* isOptedOut) return;
35898
36620
  const versionCheck = yield* VersionCheck;
35899
36621
  if (options?.quiet !== true) {
35900
- const cached = yield* versionCheck.cachedLatest;
35901
- if (cached && isNewerVersion(cached, currentVersion)) {
36622
+ let latest = yield* versionCheck.cachedLatest;
36623
+ if (latest === void 0) latest = yield* versionCheck.fetchLatest;
36624
+ if (latest && isNewerVersion(latest, currentVersion)) {
35902
36625
  const installer = detectInstallerFromImportMetaUrl(installerHint);
35903
- yield* Console.error(formatNotice(currentVersion, cached, installCommand(installer)));
36626
+ yield* Console.error(formatNotice(currentVersion, latest, installCommand(installer)));
35904
36627
  }
35905
36628
  }
35906
36629
  if (yield* versionCheck.cacheStale) spawnRefresh();