@better-update/cli 0.45.0 → 0.46.1
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 +128 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -35,7 +35,7 @@ var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
|
35
35
|
|
|
36
36
|
//#endregion
|
|
37
37
|
//#region package.json
|
|
38
|
-
var version = "0.
|
|
38
|
+
var version = "0.46.1";
|
|
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
|
|
@@ -28376,8 +28397,10 @@ const androidMenu = (ctx) => Effect.gen(function* () {
|
|
|
28376
28397
|
//#endregion
|
|
28377
28398
|
//#region src/lib/credentials-generator-asc-key.ts
|
|
28378
28399
|
const toUserRole = (role) => role === "APP_MANAGER" ? AppleUtils.UserRole.APP_MANAGER : AppleUtils.UserRole.ADMIN;
|
|
28400
|
+
const ASC_API_KEY_NICKNAME_MAX_LENGTH = 30;
|
|
28401
|
+
const clampAscApiKeyNickname = (nickname) => nickname.slice(0, ASC_API_KEY_NICKNAME_MAX_LENGTH);
|
|
28379
28402
|
/** Default nickname shown in App Store Connect → Users and Access → Integrations. */
|
|
28380
|
-
const defaultAscApiKeyNickname = () => `[better-update] ${
|
|
28403
|
+
const defaultAscApiKeyNickname = () => `[better-update] ${Date.now().toString(36)}`;
|
|
28381
28404
|
const ASC_KEY_NOT_READY_PATTERN = /no resource of type|resource does not exist/iu;
|
|
28382
28405
|
const ASC_KEY_DOWNLOAD_RETRY = Schedule.exponential("1 second", 2).pipe(Schedule.intersect(Schedule.recurs(6)));
|
|
28383
28406
|
const downloadAscKeyWithRetry = (key) => Effect.tryPromise({
|
|
@@ -28411,7 +28434,7 @@ const writeRescueP8 = (keyId, p8Pem) => Effect.gen(function* () {
|
|
|
28411
28434
|
const generateAndUploadAscApiKeyViaAppleId = (api, input) => Effect.gen(function* () {
|
|
28412
28435
|
const ctx = input.context;
|
|
28413
28436
|
const key = yield* wrap("apple-create-asc-key", async () => AppleUtils.ApiKey.createAsync(ctx, {
|
|
28414
|
-
nickname: input.nickname,
|
|
28437
|
+
nickname: clampAscApiKeyNickname(input.nickname),
|
|
28415
28438
|
allAppsVisible: true,
|
|
28416
28439
|
roles: [toUserRole(input.role)],
|
|
28417
28440
|
keyType: AppleUtils.ApiKeyType.PUBLIC_API
|
|
@@ -28459,6 +28482,20 @@ const generateAndUploadAscApiKeyViaAppleId = (api, input) => Effect.gen(function
|
|
|
28459
28482
|
role: input.role
|
|
28460
28483
|
};
|
|
28461
28484
|
});
|
|
28485
|
+
/**
|
|
28486
|
+
* List the team's active App Store Connect API keys as seen on Apple (via the
|
|
28487
|
+
* cookie session). Used before auto-creating a key to avoid making a redundant
|
|
28488
|
+
* one — note a key's `.p8` is downloadable only once, so a key listed here is
|
|
28489
|
+
* usable for publishing only if its `.p8` was captured at creation (i.e. it is
|
|
28490
|
+
* already in the vault). Surfacing them lets the caller warn + respect Apple's
|
|
28491
|
+
* per-team key cap rather than blindly creating another.
|
|
28492
|
+
*/
|
|
28493
|
+
const listAscApiKeysViaAppleId = (ctx) => Effect.gen(function* () {
|
|
28494
|
+
return (yield* wrap("apple-list-asc-keys", async () => AppleUtils.ApiKey.getAsync(ctx))).filter((key) => key.attributes.isActive).map((key) => ({
|
|
28495
|
+
keyId: key.id,
|
|
28496
|
+
nickname: key.attributes.nickname
|
|
28497
|
+
}));
|
|
28498
|
+
});
|
|
28462
28499
|
|
|
28463
28500
|
//#endregion
|
|
28464
28501
|
//#region src/application/credentials-manager-ios-asc.ts
|
|
@@ -30510,7 +30547,7 @@ const ascKeyCommand = defineCommand({
|
|
|
30510
30547
|
},
|
|
30511
30548
|
nickname: {
|
|
30512
30549
|
type: "string",
|
|
30513
|
-
description: "Nickname shown in App Store Connect (defaults to a timestamped name)"
|
|
30550
|
+
description: "Nickname shown in App Store Connect (defaults to a timestamped name; Apple caps it at 30 chars, longer values are truncated)"
|
|
30514
30551
|
}
|
|
30515
30552
|
},
|
|
30516
30553
|
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
@@ -35518,6 +35555,78 @@ const statusCommand = defineCommand({
|
|
|
35518
35555
|
}), { json: "value" })
|
|
35519
35556
|
});
|
|
35520
35557
|
|
|
35558
|
+
//#endregion
|
|
35559
|
+
//#region src/application/submit-asc-key.ts
|
|
35560
|
+
const ROLE_CHOICES = [{
|
|
35561
|
+
value: "ADMIN",
|
|
35562
|
+
label: "ADMIN (default)"
|
|
35563
|
+
}, {
|
|
35564
|
+
value: "APP_MANAGER",
|
|
35565
|
+
label: "APP_MANAGER (least privilege for app management)"
|
|
35566
|
+
}];
|
|
35567
|
+
const CREATE_CHOICE = "__create__";
|
|
35568
|
+
/** Best-effort: write the resolved id back to eas.json so the next run reuses it. */
|
|
35569
|
+
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.`)));
|
|
35570
|
+
/** Log in, warn about any existing team keys, then (with consent) create + persist. */
|
|
35571
|
+
const createAndPersist = (input) => Effect.gen(function* () {
|
|
35572
|
+
const auth = yield* AppleAuth;
|
|
35573
|
+
const session = yield* auth.ensureLoggedIn();
|
|
35574
|
+
const ctx = auth.buildRequestContext(session);
|
|
35575
|
+
const teamKeys = yield* listAscApiKeysViaAppleId(ctx).pipe(Effect.orElseSucceed(() => []));
|
|
35576
|
+
const hasTeamKeys = teamKeys.length > 0;
|
|
35577
|
+
if (hasTeamKeys) {
|
|
35578
|
+
yield* printHuman(`Your Apple team already has ${String(teamKeys.length)} App Store Connect API key(s): ${teamKeys.map((key) => key.nickname).join(", ")}.`);
|
|
35579
|
+
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.");
|
|
35580
|
+
}
|
|
35581
|
+
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;
|
|
35582
|
+
const role = yield* promptSelect("Select a role for the generated API key", ROLE_CHOICES);
|
|
35583
|
+
yield* printHuman("Creating an App Store Connect API key via your Apple ID...");
|
|
35584
|
+
const created = yield* generateAndUploadAscApiKeyViaAppleId(input.api, {
|
|
35585
|
+
context: ctx,
|
|
35586
|
+
appleTeamIdentifier: session.teamId,
|
|
35587
|
+
nickname: defaultAscApiKeyNickname(),
|
|
35588
|
+
role
|
|
35589
|
+
});
|
|
35590
|
+
yield* printHuman(`Created and stored ASC API key ${created.keyId}.`);
|
|
35591
|
+
yield* persist(input, created.id);
|
|
35592
|
+
return created.id;
|
|
35593
|
+
});
|
|
35594
|
+
/**
|
|
35595
|
+
* Resolve an ASC API key id to upload a `submit` build with when none is set in
|
|
35596
|
+
* the submit profile. Reuses a stored vault key when possible (the only keys we
|
|
35597
|
+
* hold a usable `.p8` for), else offers to create one from the Apple ID session.
|
|
35598
|
+
* Returns the resolved id, or `null` when none could be resolved — non-interactive
|
|
35599
|
+
* runs, a declined prompt, or any failure (login/create/network) degrade to `null`
|
|
35600
|
+
* so the caller falls back to queuing the submission with guidance rather than
|
|
35601
|
+
* crashing. Persists the resolved id to `eas.json` for reuse.
|
|
35602
|
+
*/
|
|
35603
|
+
const ensureAscApiKeyForSubmit = (input) => Effect.gen(function* () {
|
|
35604
|
+
if (!(yield* InteractiveMode).allow) return null;
|
|
35605
|
+
const stored = yield* input.api.ascApiKeys.list();
|
|
35606
|
+
if (stored.items.length === 1) {
|
|
35607
|
+
const [only] = stored.items;
|
|
35608
|
+
if (only !== void 0) {
|
|
35609
|
+
yield* printHuman(`Using your stored ASC API key "${only.name}" (${only.keyId}).`);
|
|
35610
|
+
yield* persist(input, only.id);
|
|
35611
|
+
return only.id;
|
|
35612
|
+
}
|
|
35613
|
+
}
|
|
35614
|
+
if (stored.items.length > 1) {
|
|
35615
|
+
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) => ({
|
|
35616
|
+
value: key.id,
|
|
35617
|
+
label: `${key.name} (${key.keyId})`
|
|
35618
|
+
})), {
|
|
35619
|
+
value: CREATE_CHOICE,
|
|
35620
|
+
label: "Create a new ASC API key from my Apple ID"
|
|
35621
|
+
}]);
|
|
35622
|
+
if (picked !== CREATE_CHOICE) {
|
|
35623
|
+
yield* persist(input, picked);
|
|
35624
|
+
return picked;
|
|
35625
|
+
}
|
|
35626
|
+
}
|
|
35627
|
+
return yield* createAndPersist(input);
|
|
35628
|
+
}).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))));
|
|
35629
|
+
|
|
35521
35630
|
//#endregion
|
|
35522
35631
|
//#region src/commands/submit/index.ts
|
|
35523
35632
|
const PLATFORMS = ["ios", "android"];
|
|
@@ -35604,7 +35713,17 @@ const runFlow = (api, projectId, args) => Effect.gen(function* () {
|
|
|
35604
35713
|
appleId: iosProfile?.appleId,
|
|
35605
35714
|
ascApiKeyId: iosProfile?.ascApiKeyId,
|
|
35606
35715
|
hasAppSpecificPassword: hasAppleAppSpecificPassword()
|
|
35607
|
-
})
|
|
35716
|
+
}) ?? (yield* Effect.gen(function* () {
|
|
35717
|
+
const resolvedKeyId = yield* ensureAscApiKeyForSubmit({
|
|
35718
|
+
api,
|
|
35719
|
+
projectRoot: args.projectRoot,
|
|
35720
|
+
profileName: args.profile
|
|
35721
|
+
});
|
|
35722
|
+
return resolvedKeyId === null ? null : {
|
|
35723
|
+
kind: "asc-api-key",
|
|
35724
|
+
ascApiKeyId: resolvedKeyId
|
|
35725
|
+
};
|
|
35726
|
+
}));
|
|
35608
35727
|
if (auth === null) {
|
|
35609
35728
|
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
35729
|
return submission;
|
|
@@ -35618,7 +35737,7 @@ const runFlow = (api, projectId, args) => Effect.gen(function* () {
|
|
|
35618
35737
|
value: args.archive.archiveUrl
|
|
35619
35738
|
},
|
|
35620
35739
|
auth,
|
|
35621
|
-
ascApiKeyId: iosProfile?.ascApiKeyId,
|
|
35740
|
+
ascApiKeyId: auth.kind === "asc-api-key" ? auth.ascApiKeyId : iosProfile?.ascApiKeyId,
|
|
35622
35741
|
config: {
|
|
35623
35742
|
bundleIdentifier: iosConfig.bundleIdentifier,
|
|
35624
35743
|
ascAppId: iosProfile?.ascAppId,
|
|
@@ -35702,7 +35821,8 @@ const submitCommand = defineCommand({
|
|
|
35702
35821
|
}
|
|
35703
35822
|
const projectId = yield* readProjectId;
|
|
35704
35823
|
const api = yield* apiClient;
|
|
35705
|
-
const
|
|
35824
|
+
const projectRoot = yield* (yield* CliRuntime).cwd;
|
|
35825
|
+
const easProfile = yield* readSubmitProfile(projectRoot, args.profile);
|
|
35706
35826
|
const archive = yield* resolveArchive(api, projectId, platform, {
|
|
35707
35827
|
id: args.id,
|
|
35708
35828
|
path: args.path,
|
|
@@ -35716,6 +35836,7 @@ const submitCommand = defineCommand({
|
|
|
35716
35836
|
yield* runFlow(api, projectId, {
|
|
35717
35837
|
platform,
|
|
35718
35838
|
profile: args.profile,
|
|
35839
|
+
projectRoot,
|
|
35719
35840
|
easProfile,
|
|
35720
35841
|
archive,
|
|
35721
35842
|
wait: args.wait,
|