@better-update/cli 0.12.0 → 0.13.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
@@ -7,7 +7,7 @@ import { Command, FetchHttpClient, FileSystem, HttpApi, HttpApiClient, HttpApiEn
7
7
  import { NodeContext } from "@effect/platform-node";
8
8
  import path, { join } from "node:path";
9
9
  import process$1 from "node:process";
10
- import * as AppleUtils from "@expo/apple-utils";
10
+ import AppleUtils from "@expo/apple-utils";
11
11
  import { cancel, confirm, isCancel, multiselect, password, select, text } from "@clack/prompts";
12
12
  import { once } from "node:events";
13
13
  import { createServer } from "node:http";
@@ -28,7 +28,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
28
28
 
29
29
  //#endregion
30
30
  //#region package.json
31
- var version = "0.12.0";
31
+ var version = "0.13.0";
32
32
 
33
33
  //#endregion
34
34
  //#region src/lib/interactive-mode.ts
@@ -5278,17 +5278,30 @@ const DISTRIBUTION_TO_CERTIFICATE_TYPE = {
5278
5278
  DEVELOPMENT: AppleUtils.CertificateType.IOS_DEVELOPMENT
5279
5279
  };
5280
5280
  var AppleIdGenerateFailedError = class extends Data.TaggedError("AppleIdGenerateFailedError") {};
5281
+ const CERT_LIMIT_PATTERN = /already have a current.*certificate|pending certificate request/iu;
5282
+ const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
5281
5283
  const wrap = (step, run) => Effect.tryPromise({
5282
5284
  try: run,
5283
5285
  catch: (cause) => new AppleIdGenerateFailedError({
5284
5286
  step,
5285
- message: cause instanceof Error ? cause.message : String(cause)
5287
+ message: messageOf(cause)
5286
5288
  })
5287
5289
  });
5290
+ const wrapCertificateCreate = (run) => Effect.tryPromise({
5291
+ try: run,
5292
+ catch: (cause) => {
5293
+ const message = messageOf(cause);
5294
+ if (CERT_LIMIT_PATTERN.test(message)) return new CertificateLimitError({ message });
5295
+ return new AppleIdGenerateFailedError({
5296
+ step: "apple-create-certificate",
5297
+ message
5298
+ });
5299
+ }
5300
+ });
5288
5301
  const generateAndUploadDistributionCertificateViaAppleId = (api, input) => Effect.gen(function* () {
5289
5302
  const ctx = input.context;
5290
5303
  const certificateType = input.certificateType === "IOS_DEVELOPMENT" ? AppleUtils.CertificateType.IOS_DEVELOPMENT : AppleUtils.CertificateType.IOS_DISTRIBUTION;
5291
- const result = yield* wrap("apple-create-certificate", async () => AppleUtils.createCertificateAndP12Async(ctx, { certificateType }));
5304
+ const result = yield* wrapCertificateCreate(async () => AppleUtils.createCertificateAndP12Async(ctx, { certificateType }));
5292
5305
  const metadata = yield* extractMetadataFromP12({
5293
5306
  p12Base64: result.certificateP12,
5294
5307
  password: result.password
@@ -5312,6 +5325,16 @@ const generateAndUploadDistributionCertificateViaAppleId = (api, input) => Effec
5312
5325
  developerPortalIdentifier: result.certificate.id
5313
5326
  };
5314
5327
  });
5328
+ const listDistributionCertsViaAppleId = (ctx, certificateType = "IOS_DISTRIBUTION") => Effect.gen(function* () {
5329
+ const filter = certificateType === "IOS_DEVELOPMENT" ? AppleUtils.CertificateType.IOS_DEVELOPMENT : AppleUtils.CertificateType.IOS_DISTRIBUTION;
5330
+ return (yield* wrap("apple-list-certificates", async () => AppleUtils.Certificate.getAsync(ctx, { query: { filter: { certificateType: filter } } }))).map((entry) => ({
5331
+ developerPortalIdentifier: entry.id,
5332
+ serialNumber: entry.attributes.serialNumber,
5333
+ displayName: entry.attributes.displayName,
5334
+ expirationDate: entry.attributes.expirationDate
5335
+ }));
5336
+ });
5337
+ const revokeDistributionCertViaAppleId = (ctx, developerPortalIdentifier) => wrap("apple-revoke-certificate", async () => AppleUtils.Certificate.deleteAsync(ctx, { id: developerPortalIdentifier }));
5315
5338
  const findOrCreateBundleId = (ctx, bundleIdentifier) => Effect.gen(function* () {
5316
5339
  const existing = yield* wrap("apple-find-bundle-id", async () => AppleUtils.BundleId.findAsync(ctx, { identifier: bundleIdentifier }));
5317
5340
  if (existing !== null) return existing.id;
@@ -5393,13 +5416,66 @@ const chooseIosSetupPath = (api) => Effect.gen(function* () {
5393
5416
  label: "Use an App Store Connect API key"
5394
5417
  }]);
5395
5418
  });
5419
+ const interactiveAppleIdCertLimitRecover = (ctx) => Effect.gen(function* () {
5420
+ yield* Console.log("");
5421
+ yield* Console.log("Apple reports the certificate limit was hit (max 3 distribution certs per team).");
5422
+ const certs = yield* listDistributionCertsViaAppleId(ctx, "IOS_DISTRIBUTION");
5423
+ if (certs.length === 0) return yield* new AppleIdGenerateFailedError({
5424
+ step: "limit-recover",
5425
+ message: "Apple says the certificate limit is hit but no existing certificates were returned."
5426
+ });
5427
+ const toRevoke = yield* promptMultiSelect("Select one or more certificates to revoke before retrying", certs.map((entry) => ({
5428
+ value: entry.developerPortalIdentifier,
5429
+ label: `${entry.serialNumber.slice(0, 12)}… (${entry.displayName}, exp ${entry.expirationDate.slice(0, 10)})`
5430
+ })), { required: true });
5431
+ yield* Effect.forEach(toRevoke, (id) => revokeDistributionCertViaAppleId(ctx, id), { concurrency: "inherit" });
5432
+ yield* Console.log(`Revoked ${toRevoke.length} certificate(s); retrying generation...`);
5433
+ });
5434
+ const generateDistributionCertViaAppleIdInteractive = (api, ctx) => Effect.gen(function* () {
5435
+ yield* Console.log("Generating distribution certificate via Apple ID...");
5436
+ const generate = generateAndUploadDistributionCertificateViaAppleId(api, { context: ctx });
5437
+ return yield* generate.pipe(Effect.catchTag("CertificateLimitError", () => interactiveAppleIdCertLimitRecover(ctx).pipe(Effect.flatMap(() => generate))));
5438
+ });
5439
+ const GENERATE_NEW = "__generate__";
5440
+ const chooseDistributionCertViaAppleId = (api, ctx, appleTeamId) => Effect.gen(function* () {
5441
+ const items = (yield* api.appleDistributionCertificates.list()).items.filter((cert) => cert.appleTeamId === appleTeamId);
5442
+ if (items.length === 0) {
5443
+ const created = yield* generateDistributionCertViaAppleIdInteractive(api, ctx);
5444
+ return {
5445
+ id: created.id,
5446
+ appleTeamId: created.appleTeamId
5447
+ };
5448
+ }
5449
+ const choice = yield* promptSelect("Select a distribution certificate (or 'generate' for a fresh one)", [{
5450
+ value: GENERATE_NEW,
5451
+ label: "Generate a new distribution certificate"
5452
+ }, ...items.map((cert) => ({
5453
+ value: cert.id,
5454
+ label: `${cert.serialNumber.slice(0, 12)}… (team ${cert.appleTeamId})`
5455
+ }))]);
5456
+ if (choice === GENERATE_NEW) {
5457
+ const created = yield* generateDistributionCertViaAppleIdInteractive(api, ctx);
5458
+ return {
5459
+ id: created.id,
5460
+ appleTeamId: created.appleTeamId
5461
+ };
5462
+ }
5463
+ const cert = items.find((entry) => entry.id === choice);
5464
+ if (cert === void 0) return yield* new AppleIdGenerateFailedError({
5465
+ step: "pick-certificate",
5466
+ message: `Selected certificate ${choice} not found after listing`
5467
+ });
5468
+ return {
5469
+ id: cert.id,
5470
+ appleTeamId: cert.appleTeamId
5471
+ };
5472
+ });
5396
5473
  const setupIosViaAppleId = (api, input) => Effect.gen(function* () {
5397
5474
  const auth = yield* AppleAuth;
5398
5475
  const session = yield* auth.ensureLoggedIn();
5399
5476
  const ctx = auth.buildRequestContext(session);
5400
5477
  yield* Console.log(`Logged in as ${session.username}. Team: ${session.teamName ?? session.teamId} (${session.teamId}).`);
5401
- yield* Console.log("Generating distribution certificate via Apple ID...");
5402
- const cert = yield* generateAndUploadDistributionCertificateViaAppleId(api, { context: ctx });
5478
+ const cert = yield* chooseDistributionCertViaAppleId(api, ctx, session.teamId);
5403
5479
  const distributionType = IOS_DISTRIBUTION_TO_TYPE[input.distribution];
5404
5480
  yield* Console.log("Generating provisioning profile via Apple ID...");
5405
5481
  const profile = yield* generateAndUploadProvisioningProfileViaAppleId(api, {