@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 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.36.2";
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
  */
@@ -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((item) => ({
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 easJsonPath = path.join(projectRoot, "eas.json");
24492
+ const filePath = easJsonPath(projectRoot);
24220
24493
  const fs = yield* FileSystem.FileSystem;
24221
- if (!(yield* fs.exists(easJsonPath))) {
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 ${easJsonPath} with defaults?`) : true)) {
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: easJsonPath
24500
+ path: filePath
24237
24501
  };
24238
24502
  }
24239
- yield* writeEasJson(easJsonPath, DEFAULT_EAS_JSON);
24240
- 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}.`);
24241
24505
  return {
24242
- action: "overwritten",
24243
- path: easJsonPath,
24244
- profiles: [...DEFAULT_PROFILES]
24506
+ action: exists ? "overwritten" : "created",
24507
+ path: filePath,
24508
+ profiles: [...DEFAULT_PROFILE_NAMES]
24245
24509
  };
24246
24510
  }
24247
- const config = yield* parseEasConfig(yield* fs.readFileString(easJsonPath).pipe(Effect.mapError((cause) => new BuildProfileError({ message: `Failed to read eas.json: ${cause.message}` }))));
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 = DEFAULT_PROFILES.filter((name) => !existingProfiles.includes(name));
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: easJsonPath,
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: easJsonPath
24537
+ path: filePath
24264
24538
  };
24265
24539
  }
24266
- const additions = Object.fromEntries(missing.map((name) => [name, DEFAULT_EAS_JSON.build[name]]));
24267
- yield* writeEasJson(easJsonPath, {
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", missing.join(", ")],
24278
- ["Path", easJsonPath]
24544
+ ["Added", result.added.join(", ")],
24545
+ ["Path", result.path]
24279
24546
  ]);
24280
24547
  return {
24281
24548
  action: "topped-up",
24282
- path: easJsonPath,
24549
+ path: result.path,
24283
24550
  existing: existingProfiles,
24284
- added: [...missing]
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((item) => ({
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 localId = yield* promptSelect("Select a distribution certificate to revoke", certs.items.map((cert) => ({
26696
- value: cert.id,
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 localId = yield* promptSelect("Select a push key to revoke", items.map((key) => ({
26726
- value: key.id,
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 pushKeyId = yield* promptSelect("Select a push key to bind", keys.items.map((key) => ({
26867
- value: key.id,
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 pushKeyId = choice === "add" ? yield* createNewPushKeyForBundle(ctx, config.appleTeamId) : yield* promptSelect("Select a push key to bind", keys.items.map((key) => ({
26887
- value: key.id,
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 chosen = yield* promptSelect("Select a push key to revoke", items.map((entry) => ({
28860
- value: entry.id,
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
- ...yield* persistLink(projectRoot, project.id, hasExpoConfig)
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
- ...yield* persistLink(projectRoot, yield* Effect.gen(function* () {
32048
- if (existing) {
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
- const cached = yield* versionCheck.cachedLatest;
35877
- 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)) {
35878
36625
  const installer = detectInstallerFromImportMetaUrl(installerHint);
35879
- yield* Console.error(formatNotice(currentVersion, cached, installCommand(installer)));
36626
+ yield* Console.error(formatNotice(currentVersion, latest, installCommand(installer)));
35880
36627
  }
35881
36628
  }
35882
36629
  if (yield* versionCheck.cacheStale) spawnRefresh();