@better-update/cli 0.45.0 → 0.46.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.45.0";
38
+ var version = "0.46.0";
39
39
 
40
40
  //#endregion
41
41
  //#region src/lib/interactive-mode.ts
@@ -5055,6 +5055,27 @@ const writeEasJsonPatch = (projectRoot, patch) => Effect.gen(function* () {
5055
5055
  return filePath;
5056
5056
  });
5057
5057
  /**
5058
+ * Set `submit.<profileName>.ios.ascApiKeyId` in `eas.json`, preserving every
5059
+ * other submit profile and key. Used after auto-resolving/creating an ASC API
5060
+ * key during `submit` so the next run reuses it instead of creating another.
5061
+ */
5062
+ const setSubmitProfileAscApiKeyId = (projectRoot, profileName, ascApiKeyId) => Effect.gen(function* () {
5063
+ const existing = (yield* readEasJsonRaw(projectRoot)) ?? {};
5064
+ const submit = isRecord$1(existing["submit"]) ? existing["submit"] : {};
5065
+ const profile = isRecord$1(submit[profileName]) ? submit[profileName] : {};
5066
+ const ios = isRecord$1(profile["ios"]) ? profile["ios"] : {};
5067
+ return yield* writeEasJsonPatch(projectRoot, { submit: {
5068
+ ...submit,
5069
+ [profileName]: {
5070
+ ...profile,
5071
+ ios: {
5072
+ ...ios,
5073
+ ascApiKeyId
5074
+ }
5075
+ }
5076
+ } });
5077
+ });
5078
+ /**
5058
5079
  * Default `build` profiles scaffolded by `init` / `build configure`. Mirrors the
5059
5080
  * EAS three-tier convention: `development` (dev-client, internal), `preview`
5060
5081
  * (internal QA) and `production` (store). Keep in sync with the build-profile
@@ -28459,6 +28480,20 @@ const generateAndUploadAscApiKeyViaAppleId = (api, input) => Effect.gen(function
28459
28480
  role: input.role
28460
28481
  };
28461
28482
  });
28483
+ /**
28484
+ * List the team's active App Store Connect API keys as seen on Apple (via the
28485
+ * cookie session). Used before auto-creating a key to avoid making a redundant
28486
+ * one — note a key's `.p8` is downloadable only once, so a key listed here is
28487
+ * usable for publishing only if its `.p8` was captured at creation (i.e. it is
28488
+ * already in the vault). Surfacing them lets the caller warn + respect Apple's
28489
+ * per-team key cap rather than blindly creating another.
28490
+ */
28491
+ const listAscApiKeysViaAppleId = (ctx) => Effect.gen(function* () {
28492
+ return (yield* wrap("apple-list-asc-keys", async () => AppleUtils.ApiKey.getAsync(ctx))).filter((key) => key.attributes.isActive).map((key) => ({
28493
+ keyId: key.id,
28494
+ nickname: key.attributes.nickname
28495
+ }));
28496
+ });
28462
28497
 
28463
28498
  //#endregion
28464
28499
  //#region src/application/credentials-manager-ios-asc.ts
@@ -35518,6 +35553,78 @@ const statusCommand = defineCommand({
35518
35553
  }), { json: "value" })
35519
35554
  });
35520
35555
 
35556
+ //#endregion
35557
+ //#region src/application/submit-asc-key.ts
35558
+ const ROLE_CHOICES = [{
35559
+ value: "ADMIN",
35560
+ label: "ADMIN (default)"
35561
+ }, {
35562
+ value: "APP_MANAGER",
35563
+ label: "APP_MANAGER (least privilege for app management)"
35564
+ }];
35565
+ const CREATE_CHOICE = "__create__";
35566
+ /** Best-effort: write the resolved id back to eas.json so the next run reuses it. */
35567
+ const persist = (input, keyId) => setSubmitProfileAscApiKeyId(input.projectRoot, input.profileName, keyId).pipe(Effect.flatMap((path) => printHuman(`Saved ascApiKeyId to ${path} (submit profile "${input.profileName}") for reuse.`)), Effect.catchAll((error) => printHuman(`Note: could not write ascApiKeyId to eas.json (${error.message}). Add it manually to reuse this key.`)));
35568
+ /** Log in, warn about any existing team keys, then (with consent) create + persist. */
35569
+ const createAndPersist = (input) => Effect.gen(function* () {
35570
+ const auth = yield* AppleAuth;
35571
+ const session = yield* auth.ensureLoggedIn();
35572
+ const ctx = auth.buildRequestContext(session);
35573
+ const teamKeys = yield* listAscApiKeysViaAppleId(ctx).pipe(Effect.orElseSucceed(() => []));
35574
+ const hasTeamKeys = teamKeys.length > 0;
35575
+ if (hasTeamKeys) {
35576
+ yield* printHuman(`Your Apple team already has ${String(teamKeys.length)} App Store Connect API key(s): ${teamKeys.map((key) => key.nickname).join(", ")}.`);
35577
+ yield* printHuman("A key's .p8 is downloadable only once at creation, so an existing key is reusable only if you still have its .p8 (import it with `credentials upload-asc-key`). Apple also caps the number of keys per team.");
35578
+ }
35579
+ if (!(yield* promptConfirm(hasTeamKeys ? "Create a new ASC API key anyway?" : "No App Store Connect API key found. Create one now from your Apple ID?", { initialValue: !hasTeamKeys }))) return null;
35580
+ const role = yield* promptSelect("Select a role for the generated API key", ROLE_CHOICES);
35581
+ yield* printHuman("Creating an App Store Connect API key via your Apple ID...");
35582
+ const created = yield* generateAndUploadAscApiKeyViaAppleId(input.api, {
35583
+ context: ctx,
35584
+ appleTeamIdentifier: session.teamId,
35585
+ nickname: defaultAscApiKeyNickname(),
35586
+ role
35587
+ });
35588
+ yield* printHuman(`Created and stored ASC API key ${created.keyId}.`);
35589
+ yield* persist(input, created.id);
35590
+ return created.id;
35591
+ });
35592
+ /**
35593
+ * Resolve an ASC API key id to upload a `submit` build with when none is set in
35594
+ * the submit profile. Reuses a stored vault key when possible (the only keys we
35595
+ * hold a usable `.p8` for), else offers to create one from the Apple ID session.
35596
+ * Returns the resolved id, or `null` when none could be resolved — non-interactive
35597
+ * runs, a declined prompt, or any failure (login/create/network) degrade to `null`
35598
+ * so the caller falls back to queuing the submission with guidance rather than
35599
+ * crashing. Persists the resolved id to `eas.json` for reuse.
35600
+ */
35601
+ const ensureAscApiKeyForSubmit = (input) => Effect.gen(function* () {
35602
+ if (!(yield* InteractiveMode).allow) return null;
35603
+ const stored = yield* input.api.ascApiKeys.list();
35604
+ if (stored.items.length === 1) {
35605
+ const [only] = stored.items;
35606
+ if (only !== void 0) {
35607
+ yield* printHuman(`Using your stored ASC API key "${only.name}" (${only.keyId}).`);
35608
+ yield* persist(input, only.id);
35609
+ return only.id;
35610
+ }
35611
+ }
35612
+ if (stored.items.length > 1) {
35613
+ const picked = yield* promptSelect("No ASC API key in this submit profile. Pick one to use, or create a new one:", [...stored.items.map((key) => ({
35614
+ value: key.id,
35615
+ label: `${key.name} (${key.keyId})`
35616
+ })), {
35617
+ value: CREATE_CHOICE,
35618
+ label: "Create a new ASC API key from my Apple ID"
35619
+ }]);
35620
+ if (picked !== CREATE_CHOICE) {
35621
+ yield* persist(input, picked);
35622
+ return picked;
35623
+ }
35624
+ }
35625
+ return yield* createAndPersist(input);
35626
+ }).pipe(Effect.catchAll((error) => printHuman(`Could not set up an App Store Connect API key (${messageOf(error)}). The submission was queued — create one with \`credentials generate asc-key\` and re-run.`).pipe(Effect.as(null))));
35627
+
35521
35628
  //#endregion
35522
35629
  //#region src/commands/submit/index.ts
35523
35630
  const PLATFORMS = ["ios", "android"];
@@ -35604,7 +35711,17 @@ const runFlow = (api, projectId, args) => Effect.gen(function* () {
35604
35711
  appleId: iosProfile?.appleId,
35605
35712
  ascApiKeyId: iosProfile?.ascApiKeyId,
35606
35713
  hasAppSpecificPassword: hasAppleAppSpecificPassword()
35607
- });
35714
+ }) ?? (yield* Effect.gen(function* () {
35715
+ const resolvedKeyId = yield* ensureAscApiKeyForSubmit({
35716
+ api,
35717
+ projectRoot: args.projectRoot,
35718
+ profileName: args.profile
35719
+ });
35720
+ return resolvedKeyId === null ? null : {
35721
+ kind: "asc-api-key",
35722
+ ascApiKeyId: resolvedKeyId
35723
+ };
35724
+ }));
35608
35725
  if (auth === null) {
35609
35726
  yield* printHuman("iOS submission queued. Add ascApiKeyId to the eas.json submit profile, or set appleId + the EXPO_APPLE_APP_SPECIFIC_PASSWORD env var, to enable client-side altool upload.");
35610
35727
  return submission;
@@ -35618,7 +35735,7 @@ const runFlow = (api, projectId, args) => Effect.gen(function* () {
35618
35735
  value: args.archive.archiveUrl
35619
35736
  },
35620
35737
  auth,
35621
- ascApiKeyId: iosProfile?.ascApiKeyId,
35738
+ ascApiKeyId: auth.kind === "asc-api-key" ? auth.ascApiKeyId : iosProfile?.ascApiKeyId,
35622
35739
  config: {
35623
35740
  bundleIdentifier: iosConfig.bundleIdentifier,
35624
35741
  ascAppId: iosProfile?.ascAppId,
@@ -35702,7 +35819,8 @@ const submitCommand = defineCommand({
35702
35819
  }
35703
35820
  const projectId = yield* readProjectId;
35704
35821
  const api = yield* apiClient;
35705
- const easProfile = yield* readSubmitProfile(yield* (yield* CliRuntime).cwd, args.profile);
35822
+ const projectRoot = yield* (yield* CliRuntime).cwd;
35823
+ const easProfile = yield* readSubmitProfile(projectRoot, args.profile);
35706
35824
  const archive = yield* resolveArchive(api, projectId, platform, {
35707
35825
  id: args.id,
35708
35826
  path: args.path,
@@ -35716,6 +35834,7 @@ const submitCommand = defineCommand({
35716
35834
  yield* runFlow(api, projectId, {
35717
35835
  platform,
35718
35836
  profile: args.profile,
35837
+ projectRoot,
35719
35838
  easProfile,
35720
35839
  archive,
35721
35840
  wait: args.wait,