@better-update/cli 0.44.1 → 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 +322 -15
- 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.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
|
|
@@ -23861,7 +23882,7 @@ const wrapKeyCreate = (run) => Effect.tryPromise({
|
|
|
23861
23882
|
});
|
|
23862
23883
|
}
|
|
23863
23884
|
});
|
|
23864
|
-
const writeRescueP8 = (keyId, p8Pem) => Effect.gen(function* () {
|
|
23885
|
+
const writeRescueP8$1 = (keyId, p8Pem) => Effect.gen(function* () {
|
|
23865
23886
|
const fs = yield* FileSystem.FileSystem;
|
|
23866
23887
|
const filePath = `AuthKey_${keyId}.p8`;
|
|
23867
23888
|
yield* fs.writeFileString(filePath, p8Pem, { mode: 384 });
|
|
@@ -23892,7 +23913,7 @@ const generateAndUploadApnsKeyViaAppleId = (api, input) => Effect.gen(function*
|
|
|
23892
23913
|
...compact({ appleTeamName: toOptional(input.appleTeamName) })
|
|
23893
23914
|
} });
|
|
23894
23915
|
}).pipe(Effect.catchAll((cause) => Effect.gen(function* () {
|
|
23895
|
-
const rescuePath = yield* writeRescueP8(key.id, p8Pem).pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
23916
|
+
const rescuePath = yield* writeRescueP8$1(key.id, p8Pem).pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
23896
23917
|
const where = rescuePath === null ? "could not be saved locally and is now unrecoverable" : `was saved to ${rescuePath} — re-import with \`credentials generate push-key --p8 ${rescuePath} --key-id ${key.id} --apple-team-id ${input.appleTeamIdentifier}\``;
|
|
23897
23918
|
return yield* new AppleIdGenerateFailedError({
|
|
23898
23919
|
step: "store-apns-key",
|
|
@@ -28373,6 +28394,107 @@ const androidMenu = (ctx) => Effect.gen(function* () {
|
|
|
28373
28394
|
yield* androidMenu(ctx);
|
|
28374
28395
|
});
|
|
28375
28396
|
|
|
28397
|
+
//#endregion
|
|
28398
|
+
//#region src/lib/credentials-generator-asc-key.ts
|
|
28399
|
+
const toUserRole = (role) => role === "APP_MANAGER" ? AppleUtils.UserRole.APP_MANAGER : AppleUtils.UserRole.ADMIN;
|
|
28400
|
+
/** Default nickname shown in App Store Connect → Users and Access → Integrations. */
|
|
28401
|
+
const defaultAscApiKeyNickname = () => `[better-update] ${(/* @__PURE__ */ new Date()).toISOString()}`;
|
|
28402
|
+
const ASC_KEY_NOT_READY_PATTERN = /no resource of type|resource does not exist/iu;
|
|
28403
|
+
const ASC_KEY_DOWNLOAD_RETRY = Schedule.exponential("1 second", 2).pipe(Schedule.intersect(Schedule.recurs(6)));
|
|
28404
|
+
const downloadAscKeyWithRetry = (key) => Effect.tryPromise({
|
|
28405
|
+
try: async () => key.downloadAsync(),
|
|
28406
|
+
catch: (cause) => new AppleIdGenerateFailedError({
|
|
28407
|
+
step: "apple-download-asc-key",
|
|
28408
|
+
message: messageOf(cause)
|
|
28409
|
+
})
|
|
28410
|
+
}).pipe(Effect.flatMap((pem) => pem === null || pem.length === 0 ? Effect.fail(new AppleIdGenerateFailedError({
|
|
28411
|
+
step: "apple-download-asc-key",
|
|
28412
|
+
message: "App Store Connect returned no private key for the new API key — it may already have been downloaded. A key can only be downloaded once; create a new one or upload the .p8 manually with `credentials upload-asc-key`."
|
|
28413
|
+
})) : Effect.succeed(pem)), Effect.retry({
|
|
28414
|
+
while: (error) => ASC_KEY_NOT_READY_PATTERN.test(error.message),
|
|
28415
|
+
schedule: ASC_KEY_DOWNLOAD_RETRY
|
|
28416
|
+
}), Effect.catchIf((error) => ASC_KEY_NOT_READY_PATTERN.test(error.message), () => Effect.fail(new AppleIdGenerateFailedError({
|
|
28417
|
+
step: "apple-download-asc-key",
|
|
28418
|
+
message: "App Store Connect is still provisioning the new API key (this can take up to a minute). The key was created — re-run shortly, or download the .p8 from App Store Connect → Users and Access → Integrations and import it with `credentials upload-asc-key`."
|
|
28419
|
+
}))));
|
|
28420
|
+
const writeRescueP8 = (keyId, p8Pem) => Effect.gen(function* () {
|
|
28421
|
+
const fs = yield* FileSystem.FileSystem;
|
|
28422
|
+
const filePath = `AuthKey_${keyId}.p8`;
|
|
28423
|
+
yield* fs.writeFileString(filePath, p8Pem, { mode: 384 });
|
|
28424
|
+
return filePath;
|
|
28425
|
+
});
|
|
28426
|
+
/**
|
|
28427
|
+
* Create an App Store Connect API key from the Apple ID cookie session, download
|
|
28428
|
+
* its one-shot `.p8`, resolve the issuer id, and store the sealed envelope in the
|
|
28429
|
+
* vault — the zero-knowledge equivalent of downloading a key from App Store Connect
|
|
28430
|
+
* and running `credentials upload-asc-key`, but without the manual round-trip.
|
|
28431
|
+
*/
|
|
28432
|
+
const generateAndUploadAscApiKeyViaAppleId = (api, input) => Effect.gen(function* () {
|
|
28433
|
+
const ctx = input.context;
|
|
28434
|
+
const key = yield* wrap("apple-create-asc-key", async () => AppleUtils.ApiKey.createAsync(ctx, {
|
|
28435
|
+
nickname: input.nickname,
|
|
28436
|
+
allAppsVisible: true,
|
|
28437
|
+
roles: [toUserRole(input.role)],
|
|
28438
|
+
keyType: AppleUtils.ApiKeyType.PUBLIC_API
|
|
28439
|
+
}));
|
|
28440
|
+
const p8Pem = yield* downloadAscKeyWithRetry(key);
|
|
28441
|
+
const displayName = input.name ?? key.id;
|
|
28442
|
+
const stored = yield* Effect.gen(function* () {
|
|
28443
|
+
const issuerId = (yield* wrap("apple-fetch-asc-key", async () => AppleUtils.ApiKey.infoAsync(ctx, { id: key.id }))).attributes.provider?.id;
|
|
28444
|
+
if (issuerId === void 0 || issuerId.length === 0) return yield* new AppleIdGenerateFailedError({
|
|
28445
|
+
step: "resolve-issuer-id",
|
|
28446
|
+
message: "App Store Connect did not return an issuer ID for the new key. Find it under Users and Access → Integrations and import the .p8 with `credentials upload-asc-key`."
|
|
28447
|
+
});
|
|
28448
|
+
const metadata = compact({
|
|
28449
|
+
name: displayName,
|
|
28450
|
+
keyId: key.id,
|
|
28451
|
+
issuerId,
|
|
28452
|
+
appleTeamIdentifier: input.appleTeamIdentifier
|
|
28453
|
+
});
|
|
28454
|
+
const envelope = yield* sealForUpload({
|
|
28455
|
+
session: yield* openVaultSessionInteractive(api),
|
|
28456
|
+
credentialType: "asc-api-key",
|
|
28457
|
+
metadata,
|
|
28458
|
+
secret: { p8Pem }
|
|
28459
|
+
});
|
|
28460
|
+
return {
|
|
28461
|
+
id: (yield* api.ascApiKeys.upload({ payload: {
|
|
28462
|
+
...toUploadEnvelope(envelope),
|
|
28463
|
+
...metadata
|
|
28464
|
+
} })).id,
|
|
28465
|
+
issuerId
|
|
28466
|
+
};
|
|
28467
|
+
}).pipe(Effect.catchAll((cause) => Effect.gen(function* () {
|
|
28468
|
+
const rescuePath = yield* writeRescueP8(key.id, p8Pem).pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
28469
|
+
const where = rescuePath === null ? "could not be saved locally and is now unrecoverable" : `was saved to ${rescuePath} — re-import with \`credentials upload-asc-key --p8 ${rescuePath} --key-id ${key.id}\` (find the issuer ID under App Store Connect → Users and Access → Integrations)`;
|
|
28470
|
+
return yield* new AppleIdGenerateFailedError({
|
|
28471
|
+
step: "store-asc-key",
|
|
28472
|
+
message: `Created App Store Connect API key ${key.id} on Apple but failed to store it (${messageOf(cause)}). The downloaded .p8 ${where}.`
|
|
28473
|
+
});
|
|
28474
|
+
})));
|
|
28475
|
+
return {
|
|
28476
|
+
id: stored.id,
|
|
28477
|
+
keyId: key.id,
|
|
28478
|
+
issuerId: stored.issuerId,
|
|
28479
|
+
name: displayName,
|
|
28480
|
+
role: input.role
|
|
28481
|
+
};
|
|
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
|
+
});
|
|
28497
|
+
|
|
28376
28498
|
//#endregion
|
|
28377
28499
|
//#region src/application/credentials-manager-ios-asc.ts
|
|
28378
28500
|
const uploadIosAscKey = (ctx) => Effect.gen(function* () {
|
|
@@ -28392,6 +28514,33 @@ const uploadIosAscKey = (ctx) => Effect.gen(function* () {
|
|
|
28392
28514
|
yield* Console.log("ASC API key uploaded.");
|
|
28393
28515
|
yield* printKeyValue([["ID", created.id], ["Key ID", keyId]]);
|
|
28394
28516
|
});
|
|
28517
|
+
const generateAscKeyViaAppleId = (ctx) => Effect.gen(function* () {
|
|
28518
|
+
const auth = yield* AppleAuth;
|
|
28519
|
+
const session = yield* auth.ensureLoggedIn();
|
|
28520
|
+
const role = yield* promptSelect("Select a role for the generated API key", [{
|
|
28521
|
+
value: "ADMIN",
|
|
28522
|
+
label: "ADMIN (default)"
|
|
28523
|
+
}, {
|
|
28524
|
+
value: "APP_MANAGER",
|
|
28525
|
+
label: "APP_MANAGER (least privilege for app management)"
|
|
28526
|
+
}]);
|
|
28527
|
+
yield* Console.log("Creating an App Store Connect API key via your Apple ID...");
|
|
28528
|
+
return yield* generateAndUploadAscApiKeyViaAppleId(ctx.api, {
|
|
28529
|
+
context: auth.buildRequestContext(session),
|
|
28530
|
+
appleTeamIdentifier: session.teamId,
|
|
28531
|
+
nickname: defaultAscApiKeyNickname(),
|
|
28532
|
+
role
|
|
28533
|
+
});
|
|
28534
|
+
});
|
|
28535
|
+
const createIosAscKeyViaAppleId = (ctx) => Effect.gen(function* () {
|
|
28536
|
+
const created = yield* generateAscKeyViaAppleId(ctx);
|
|
28537
|
+
yield* Console.log(`Created and stored ASC API key ${created.keyId}.`);
|
|
28538
|
+
yield* printKeyValue([
|
|
28539
|
+
["ID", created.id],
|
|
28540
|
+
["Key ID", created.keyId],
|
|
28541
|
+
["Issuer ID", created.issuerId]
|
|
28542
|
+
]);
|
|
28543
|
+
});
|
|
28395
28544
|
const bindIosAscKey = (ctx) => Effect.gen(function* () {
|
|
28396
28545
|
const keys = yield* ctx.api.ascApiKeys.list();
|
|
28397
28546
|
if (keys.items.length === 0) return yield* new MissingCredentialsError({
|
|
@@ -28428,18 +28577,26 @@ const uploadNewAscKey = (ctx) => Effect.gen(function* () {
|
|
|
28428
28577
|
});
|
|
28429
28578
|
const setupProjectAscApiKey = (ctx) => Effect.gen(function* () {
|
|
28430
28579
|
const keys = yield* ctx.api.ascApiKeys.list();
|
|
28431
|
-
const
|
|
28432
|
-
value: "
|
|
28433
|
-
label:
|
|
28580
|
+
const baseChoices = [{
|
|
28581
|
+
value: "generate",
|
|
28582
|
+
label: "Create a new ASC API key from your Apple ID (no .p8 needed)"
|
|
28434
28583
|
}, {
|
|
28435
28584
|
value: "upload",
|
|
28436
|
-
label: "Upload
|
|
28437
|
-
}]
|
|
28585
|
+
label: "Upload an existing .p8 key"
|
|
28586
|
+
}];
|
|
28587
|
+
const choice = keys.items.length === 0 ? yield* promptSelect("How would you like to set up the ASC key?", baseChoices) : yield* promptSelect("How would you like to set up the ASC key?", [{
|
|
28588
|
+
value: "existing",
|
|
28589
|
+
label: `Use an existing ASC API key (${String(keys.items.length)})`
|
|
28590
|
+
}, ...baseChoices]);
|
|
28438
28591
|
const config = yield* promptForBundleConfig(ctx);
|
|
28439
|
-
const ascKeyId =
|
|
28440
|
-
|
|
28441
|
-
|
|
28442
|
-
|
|
28592
|
+
const ascKeyId = yield* Effect.gen(function* () {
|
|
28593
|
+
if (choice === "generate") return (yield* generateAscKeyViaAppleId(ctx)).id;
|
|
28594
|
+
if (choice === "upload") return yield* uploadNewAscKey(ctx);
|
|
28595
|
+
return yield* promptSelect("Select an ASC API key to bind", keys.items.map((key) => ({
|
|
28596
|
+
value: key.id,
|
|
28597
|
+
label: `${key.name} (${key.keyId})`
|
|
28598
|
+
})));
|
|
28599
|
+
});
|
|
28443
28600
|
yield* ctx.api.iosBundleConfigurations.update({
|
|
28444
28601
|
path: { id: config.id },
|
|
28445
28602
|
payload: { ascApiKeyId: ascKeyId }
|
|
@@ -28453,6 +28610,10 @@ const iosAscKeysMenu = (ctx) => Effect.gen(function* () {
|
|
|
28453
28610
|
value: "setup",
|
|
28454
28611
|
label: "Set up your project to use an ASC API Key"
|
|
28455
28612
|
},
|
|
28613
|
+
{
|
|
28614
|
+
value: "create",
|
|
28615
|
+
label: "Create a new ASC API key from your Apple ID (no .p8 needed)"
|
|
28616
|
+
},
|
|
28456
28617
|
{
|
|
28457
28618
|
value: "upload",
|
|
28458
28619
|
label: "Add a new ASC API key"
|
|
@@ -28472,6 +28633,7 @@ const iosAscKeysMenu = (ctx) => Effect.gen(function* () {
|
|
|
28472
28633
|
]));
|
|
28473
28634
|
if (choice === "__back__") return;
|
|
28474
28635
|
if (choice === "setup") yield* safely("set up ASC key", setupProjectAscApiKey(ctx));
|
|
28636
|
+
else if (choice === "create") yield* safely("create ASC key", createIosAscKeyViaAppleId(ctx));
|
|
28475
28637
|
else if (choice === "upload") yield* safely("upload ASC key", uploadIosAscKey(ctx));
|
|
28476
28638
|
else if (choice === "bind") yield* safely("bind ASC key", bindIosAscKey(ctx));
|
|
28477
28639
|
else if (choice === "delete") yield* safely("delete ASC key", pickAndDelete(ctx, "asc-api-key", "ASC API key"));
|
|
@@ -30353,6 +30515,66 @@ const envVaultCommand = defineCommand({
|
|
|
30353
30515
|
default: "status"
|
|
30354
30516
|
});
|
|
30355
30517
|
|
|
30518
|
+
//#endregion
|
|
30519
|
+
//#region src/commands/credentials/generate-asc-key.ts
|
|
30520
|
+
const ASC_KEY_EXIT_EXTRAS = {
|
|
30521
|
+
CredentialValidationError: 2,
|
|
30522
|
+
AppleIdGenerateFailedError: 6,
|
|
30523
|
+
AppleAuthError: 4,
|
|
30524
|
+
InteractiveProhibitedError: 4
|
|
30525
|
+
};
|
|
30526
|
+
const normalizeRole = (raw) => {
|
|
30527
|
+
if (raw === void 0) return Effect.succeed("ADMIN");
|
|
30528
|
+
const upper = raw.trim().toUpperCase();
|
|
30529
|
+
if (upper === "ADMIN" || upper === "APP_MANAGER") return Effect.succeed(upper);
|
|
30530
|
+
return Effect.fail(new CredentialValidationError({ message: `Unknown ASC API key role "${raw}". Use ADMIN or APP_MANAGER.` }));
|
|
30531
|
+
};
|
|
30532
|
+
const ascKeyCommand = defineCommand({
|
|
30533
|
+
meta: {
|
|
30534
|
+
name: "asc-key",
|
|
30535
|
+
description: "Create an App Store Connect API key (.p8) directly from your Apple ID login — no manual download. Stored encrypted in your vault, it can issue certificates, sync devices, and upload builds. Requires the Account Holder to have agreed to the API Terms once under Users and Access → Integrations."
|
|
30536
|
+
},
|
|
30537
|
+
args: {
|
|
30538
|
+
role: {
|
|
30539
|
+
type: "string",
|
|
30540
|
+
description: "ADMIN (default) or APP_MANAGER (least privilege)"
|
|
30541
|
+
},
|
|
30542
|
+
name: {
|
|
30543
|
+
type: "string",
|
|
30544
|
+
description: "Display name for the stored key (defaults to the key ID)"
|
|
30545
|
+
},
|
|
30546
|
+
nickname: {
|
|
30547
|
+
type: "string",
|
|
30548
|
+
description: "Nickname shown in App Store Connect (defaults to a timestamped name)"
|
|
30549
|
+
}
|
|
30550
|
+
},
|
|
30551
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
30552
|
+
const role = yield* normalizeRole(args.role);
|
|
30553
|
+
const api = yield* apiClient;
|
|
30554
|
+
const auth = yield* AppleAuth;
|
|
30555
|
+
const session = yield* auth.ensureLoggedIn();
|
|
30556
|
+
yield* printHuman("Creating an App Store Connect API key via your Apple ID...");
|
|
30557
|
+
const created = yield* generateAndUploadAscApiKeyViaAppleId(api, {
|
|
30558
|
+
context: auth.buildRequestContext(session),
|
|
30559
|
+
appleTeamIdentifier: session.teamId,
|
|
30560
|
+
nickname: args.nickname ?? defaultAscApiKeyNickname(),
|
|
30561
|
+
role,
|
|
30562
|
+
...compact({ name: args.name })
|
|
30563
|
+
});
|
|
30564
|
+
yield* printHuman("App Store Connect API key created and stored.");
|
|
30565
|
+
yield* printHumanKeyValue([
|
|
30566
|
+
["ID", created.id],
|
|
30567
|
+
["Key ID", created.keyId],
|
|
30568
|
+
["Issuer ID", created.issuerId],
|
|
30569
|
+
["Role", created.role]
|
|
30570
|
+
]);
|
|
30571
|
+
return created;
|
|
30572
|
+
}), {
|
|
30573
|
+
exits: ASC_KEY_EXIT_EXTRAS,
|
|
30574
|
+
json: "value"
|
|
30575
|
+
})
|
|
30576
|
+
});
|
|
30577
|
+
|
|
30356
30578
|
//#endregion
|
|
30357
30579
|
//#region src/lib/credentials-generator-merchant.ts
|
|
30358
30580
|
/**
|
|
@@ -30856,6 +31078,7 @@ const generateCommand$1 = defineCommand({
|
|
|
30856
31078
|
"provisioning-profile": provisioningProfileCommand,
|
|
30857
31079
|
"push-key": pushKeyCommand$1,
|
|
30858
31080
|
"merchant-id": merchantIdCommand,
|
|
31081
|
+
"asc-key": ascKeyCommand,
|
|
30859
31082
|
"gsa-key": gsaKeyCommand
|
|
30860
31083
|
}
|
|
30861
31084
|
});
|
|
@@ -35330,6 +35553,78 @@ const statusCommand = defineCommand({
|
|
|
35330
35553
|
}), { json: "value" })
|
|
35331
35554
|
});
|
|
35332
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
|
+
|
|
35333
35628
|
//#endregion
|
|
35334
35629
|
//#region src/commands/submit/index.ts
|
|
35335
35630
|
const PLATFORMS = ["ios", "android"];
|
|
@@ -35416,7 +35711,17 @@ const runFlow = (api, projectId, args) => Effect.gen(function* () {
|
|
|
35416
35711
|
appleId: iosProfile?.appleId,
|
|
35417
35712
|
ascApiKeyId: iosProfile?.ascApiKeyId,
|
|
35418
35713
|
hasAppSpecificPassword: hasAppleAppSpecificPassword()
|
|
35419
|
-
})
|
|
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
|
+
}));
|
|
35420
35725
|
if (auth === null) {
|
|
35421
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.");
|
|
35422
35727
|
return submission;
|
|
@@ -35430,7 +35735,7 @@ const runFlow = (api, projectId, args) => Effect.gen(function* () {
|
|
|
35430
35735
|
value: args.archive.archiveUrl
|
|
35431
35736
|
},
|
|
35432
35737
|
auth,
|
|
35433
|
-
ascApiKeyId: iosProfile?.ascApiKeyId,
|
|
35738
|
+
ascApiKeyId: auth.kind === "asc-api-key" ? auth.ascApiKeyId : iosProfile?.ascApiKeyId,
|
|
35434
35739
|
config: {
|
|
35435
35740
|
bundleIdentifier: iosConfig.bundleIdentifier,
|
|
35436
35741
|
ascAppId: iosProfile?.ascAppId,
|
|
@@ -35514,7 +35819,8 @@ const submitCommand = defineCommand({
|
|
|
35514
35819
|
}
|
|
35515
35820
|
const projectId = yield* readProjectId;
|
|
35516
35821
|
const api = yield* apiClient;
|
|
35517
|
-
const
|
|
35822
|
+
const projectRoot = yield* (yield* CliRuntime).cwd;
|
|
35823
|
+
const easProfile = yield* readSubmitProfile(projectRoot, args.profile);
|
|
35518
35824
|
const archive = yield* resolveArchive(api, projectId, platform, {
|
|
35519
35825
|
id: args.id,
|
|
35520
35826
|
path: args.path,
|
|
@@ -35528,6 +35834,7 @@ const submitCommand = defineCommand({
|
|
|
35528
35834
|
yield* runFlow(api, projectId, {
|
|
35529
35835
|
platform,
|
|
35530
35836
|
profile: args.profile,
|
|
35837
|
+
projectRoot,
|
|
35531
35838
|
easProfile,
|
|
35532
35839
|
archive,
|
|
35533
35840
|
wait: args.wait,
|