@better-update/cli 0.32.0 → 0.33.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 +392 -100
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -34,7 +34,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
34
34
|
|
|
35
35
|
//#endregion
|
|
36
36
|
//#region package.json
|
|
37
|
-
var version = "0.
|
|
37
|
+
var version = "0.33.0";
|
|
38
38
|
|
|
39
39
|
//#endregion
|
|
40
40
|
//#region src/lib/interactive-mode.ts
|
|
@@ -3078,8 +3078,10 @@ const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(functio
|
|
|
3078
3078
|
const defaultAppleUtils = {
|
|
3079
3079
|
Auth: AppleUtils.Auth,
|
|
3080
3080
|
Session: AppleUtils.Session,
|
|
3081
|
+
Teams: AppleUtils.Teams,
|
|
3081
3082
|
CookieFileCache: AppleUtils.CookieFileCache
|
|
3082
3083
|
};
|
|
3084
|
+
const TEN_CHAR_TEAM_ID = /^[A-Z0-9]{10}$/u;
|
|
3083
3085
|
var AppleAuth = class extends Context.Tag("cli/AppleAuth")() {};
|
|
3084
3086
|
const sessionFromAuthState = (state) => ({
|
|
3085
3087
|
username: state.username,
|
|
@@ -3093,11 +3095,19 @@ const sessionFromInfo = (username, info) => ({
|
|
|
3093
3095
|
teamName: info.provider.name,
|
|
3094
3096
|
providerId: info.provider.providerId
|
|
3095
3097
|
});
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3098
|
+
/**
|
|
3099
|
+
* Resolve the 10-char Developer Portal Team ID for a selected App Store Connect
|
|
3100
|
+
* provider. Uses the provider's `publicProviderId` directly when it already is a
|
|
3101
|
+
* Team ID; otherwise (UUID providers) looks it up from the Developer Portal team
|
|
3102
|
+
* list by name — the only field both surfaces share. Falls back to the
|
|
3103
|
+
* `publicProviderId` if no match, preserving prior behavior.
|
|
3104
|
+
*/
|
|
3105
|
+
const resolvePortalTeamId = (appleUtils, provider) => Effect.gen(function* () {
|
|
3106
|
+
if (TEN_CHAR_TEAM_ID.test(provider.publicProviderId)) return provider.publicProviderId;
|
|
3107
|
+
return (yield* Effect.tryPromise({
|
|
3108
|
+
try: async () => appleUtils.Teams.getTeamsAsync(),
|
|
3109
|
+
catch: (cause) => new AppleAuthError$1({ message: `Failed to list Apple Developer teams: ${formatCause(cause)}` })
|
|
3110
|
+
})).find((team) => team.name === provider.name)?.teamId ?? provider.publicProviderId;
|
|
3101
3111
|
});
|
|
3102
3112
|
const restoreFromCookies = (appleUtils, cookies) => Effect.tryPromise({
|
|
3103
3113
|
try: async () => appleUtils.Auth.loginWithCookiesAsync({ cookies }),
|
|
@@ -3112,10 +3122,16 @@ const restoreFromCookies = (appleUtils, cookies) => Effect.tryPromise({
|
|
|
3112
3122
|
const resolveSessionTeam = (appleUtils, state) => Effect.gen(function* () {
|
|
3113
3123
|
const { availableProviders } = state.session;
|
|
3114
3124
|
const resolution = yield* resolveProvider(appleUtils, availableProviders, state.context.providerId ?? state.session.provider.providerId);
|
|
3115
|
-
|
|
3116
|
-
const
|
|
3117
|
-
if (
|
|
3118
|
-
|
|
3125
|
+
const switched = resolution.switched && resolution.providerId !== void 0;
|
|
3126
|
+
const provider = switched ? availableProviders.find((entry) => entry.providerId === resolution.providerId) : state.session.provider;
|
|
3127
|
+
if (provider === void 0) return yield* new AppleAuthError$1({ message: `Selected provider ${String(resolution.providerId)} not in available providers list.` });
|
|
3128
|
+
const teamId = (!switched && state.context.teamId !== void 0 && TEN_CHAR_TEAM_ID.test(state.context.teamId) ? state.context.teamId : void 0) ?? (yield* resolvePortalTeamId(appleUtils, provider));
|
|
3129
|
+
return {
|
|
3130
|
+
username: state.username,
|
|
3131
|
+
teamId,
|
|
3132
|
+
teamName: provider.name,
|
|
3133
|
+
providerId: provider.providerId
|
|
3134
|
+
};
|
|
3119
3135
|
});
|
|
3120
3136
|
const loginWithCredentials = (appleUtils, credentials) => Effect.tryPromise({
|
|
3121
3137
|
try: async () => appleUtils.Auth.loginWithUserCredentialsAsync(credentials, { autoResolveProvider: true }),
|
|
@@ -20755,6 +20771,7 @@ const DISTRIBUTION_TO_CERTIFICATE_TYPE = {
|
|
|
20755
20771
|
DEVELOPMENT: AppleUtils.CertificateType.IOS_DEVELOPMENT
|
|
20756
20772
|
};
|
|
20757
20773
|
var AppleIdGenerateFailedError = class extends Data.TaggedError("AppleIdGenerateFailedError") {};
|
|
20774
|
+
var ApnsKeyLimitError = class extends Data.TaggedError("ApnsKeyLimitError") {};
|
|
20758
20775
|
const CERT_LIMIT_PATTERN = /already have a current.*certificate|pending certificate request/iu;
|
|
20759
20776
|
const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
20760
20777
|
const wrap = (step, run) => Effect.tryPromise({
|
|
@@ -20899,6 +20916,86 @@ const generateAndUploadProvisioningProfileViaAppleId = (api, input) => Effect.ge
|
|
|
20899
20916
|
developerPortalIdentifier: created.developerPortalIdentifier
|
|
20900
20917
|
};
|
|
20901
20918
|
});
|
|
20919
|
+
const APNS_SERVICE_ID = "U27F4V844T";
|
|
20920
|
+
const APNS_KEY_LIMIT_PATTERN = /maximum allowed number of .*keys/iu;
|
|
20921
|
+
const wrapKeyCreate = (run) => Effect.tryPromise({
|
|
20922
|
+
try: run,
|
|
20923
|
+
catch: (cause) => {
|
|
20924
|
+
const message = messageOf(cause);
|
|
20925
|
+
return cause instanceof AppleUtils.Keys.MaxKeysCreatedError || APNS_KEY_LIMIT_PATTERN.test(message) ? new ApnsKeyLimitError({ message }) : new AppleIdGenerateFailedError({
|
|
20926
|
+
step: "apple-create-key",
|
|
20927
|
+
message
|
|
20928
|
+
});
|
|
20929
|
+
}
|
|
20930
|
+
});
|
|
20931
|
+
const writeRescueP8 = (keyId, p8Pem) => Effect.gen(function* () {
|
|
20932
|
+
const fs = yield* FileSystem.FileSystem;
|
|
20933
|
+
const filePath = `AuthKey_${keyId}.p8`;
|
|
20934
|
+
yield* fs.writeFileString(filePath, p8Pem, { mode: 384 });
|
|
20935
|
+
return filePath;
|
|
20936
|
+
});
|
|
20937
|
+
const generateAndUploadApnsKeyViaAppleId = (api, input) => Effect.gen(function* () {
|
|
20938
|
+
const ctx = input.context;
|
|
20939
|
+
const key = yield* wrapKeyCreate(async () => AppleUtils.Keys.createKeyAsync(ctx, {
|
|
20940
|
+
name: input.name,
|
|
20941
|
+
isApns: true
|
|
20942
|
+
}));
|
|
20943
|
+
const p8Pem = yield* wrap("apple-download-key", async () => AppleUtils.Keys.downloadKeyAsync(ctx, { id: key.id }));
|
|
20944
|
+
const metadata = {
|
|
20945
|
+
keyId: key.id,
|
|
20946
|
+
appleTeamIdentifier: input.appleTeamIdentifier
|
|
20947
|
+
};
|
|
20948
|
+
return {
|
|
20949
|
+
id: (yield* Effect.gen(function* () {
|
|
20950
|
+
const envelope = yield* sealForUpload({
|
|
20951
|
+
session: yield* openVaultSessionInteractive(api),
|
|
20952
|
+
credentialType: "push-key",
|
|
20953
|
+
metadata,
|
|
20954
|
+
secret: { p8Pem }
|
|
20955
|
+
});
|
|
20956
|
+
return yield* api.applePushKeys.upload({ payload: {
|
|
20957
|
+
...toUploadEnvelope(envelope),
|
|
20958
|
+
...metadata
|
|
20959
|
+
} });
|
|
20960
|
+
}).pipe(Effect.catchAll((cause) => Effect.gen(function* () {
|
|
20961
|
+
const rescuePath = yield* writeRescueP8(key.id, p8Pem).pipe(Effect.catchAll(() => Effect.succeed(null)));
|
|
20962
|
+
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}\``;
|
|
20963
|
+
return yield* new AppleIdGenerateFailedError({
|
|
20964
|
+
step: "store-apns-key",
|
|
20965
|
+
message: `Created APNs key ${key.id} on Apple but failed to store it (${messageOf(cause)}). The downloaded .p8 ${where}.`
|
|
20966
|
+
});
|
|
20967
|
+
})))).id,
|
|
20968
|
+
keyId: key.id,
|
|
20969
|
+
appleTeamIdentifier: input.appleTeamIdentifier,
|
|
20970
|
+
name: key.name
|
|
20971
|
+
};
|
|
20972
|
+
});
|
|
20973
|
+
const listApnsKeysViaAppleId = (ctx) => Effect.gen(function* () {
|
|
20974
|
+
const keys = yield* wrap("apple-list-keys", async () => AppleUtils.Keys.getKeysAsync(ctx));
|
|
20975
|
+
return (yield* Effect.forEach(keys, (key) => wrap("apple-get-key-info", async () => AppleUtils.Keys.getKeyInfoAsync(ctx, { id: key.id })), { concurrency: 4 })).filter((info) => info.services.some((service) => service.id === APNS_SERVICE_ID)).map((info) => ({
|
|
20976
|
+
developerPortalKeyId: info.id,
|
|
20977
|
+
name: info.name,
|
|
20978
|
+
canRevoke: info.canRevoke
|
|
20979
|
+
}));
|
|
20980
|
+
});
|
|
20981
|
+
const revokeApnsKeyViaAppleId = (ctx, developerPortalKeyId) => wrap("apple-revoke-key", async () => AppleUtils.Keys.revokeKeyAsync(ctx, { id: developerPortalKeyId }));
|
|
20982
|
+
/**
|
|
20983
|
+
* Revoke an APNs key on Apple and (optionally) delete the stored copy. Only keys
|
|
20984
|
+
* still present on the portal are revoked — one already gone upstream is treated
|
|
20985
|
+
* as `revokedOnApple: false` and still deleted locally, so cleanup never wedges.
|
|
20986
|
+
* Shared by the `revoke push-key` command and the interactive wizard.
|
|
20987
|
+
*/
|
|
20988
|
+
const revokeLocalApnsKey = (api, input) => Effect.gen(function* () {
|
|
20989
|
+
const present = (yield* listApnsKeysViaAppleId(input.context)).some((entry) => entry.developerPortalKeyId === input.keyId);
|
|
20990
|
+
if (present) yield* revokeApnsKeyViaAppleId(input.context, input.keyId);
|
|
20991
|
+
if (!input.keepLocal) yield* api.applePushKeys.delete({ path: { id: input.pushKeyId } });
|
|
20992
|
+
return {
|
|
20993
|
+
localId: input.pushKeyId,
|
|
20994
|
+
keyId: input.keyId,
|
|
20995
|
+
revokedOnApple: present,
|
|
20996
|
+
deletedLocally: !input.keepLocal
|
|
20997
|
+
};
|
|
20998
|
+
});
|
|
20902
20999
|
|
|
20903
21000
|
//#endregion
|
|
20904
21001
|
//#region src/lib/ios-bundle-config-upsert.ts
|
|
@@ -20971,6 +21068,36 @@ const interactiveAppleIdCertLimitRecover = (ctx) => Effect.gen(function* () {
|
|
|
20971
21068
|
yield* Effect.forEach(toRevoke, (id) => revokeDistributionCertViaAppleId(ctx, id), { concurrency: "inherit" });
|
|
20972
21069
|
yield* Console.log(`Revoked ${toRevoke.length} certificate(s); retrying generation...`);
|
|
20973
21070
|
});
|
|
21071
|
+
const defaultApnsKeyName = () => `better-update APNs (${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)})`;
|
|
21072
|
+
const apnsKeyLimitRecover = (ctx) => Effect.gen(function* () {
|
|
21073
|
+
yield* Console.log("");
|
|
21074
|
+
yield* Console.log("Apple reports the APNs key limit was hit (max 2 keys per team).");
|
|
21075
|
+
const revocable = (yield* listApnsKeysViaAppleId(ctx)).filter((entry) => entry.canRevoke);
|
|
21076
|
+
if (revocable.length === 0) return yield* new CredentialValidationError({ message: "Apple says the APNs key limit is hit but no revocable keys were returned." });
|
|
21077
|
+
const toRevoke = yield* promptMultiSelect("Select one or more APNs keys to revoke before retrying", revocable.map((entry) => ({
|
|
21078
|
+
value: entry.developerPortalKeyId,
|
|
21079
|
+
label: `${entry.name} (${entry.developerPortalKeyId})`
|
|
21080
|
+
})), { required: true });
|
|
21081
|
+
yield* Effect.forEach(toRevoke, (id) => revokeApnsKeyViaAppleId(ctx, id), { concurrency: "inherit" });
|
|
21082
|
+
yield* Console.log(`Revoked ${toRevoke.length} key(s); retrying creation...`);
|
|
21083
|
+
});
|
|
21084
|
+
/**
|
|
21085
|
+
* Log in with Apple ID, create a fresh APNs `.p8` on the portal, download it, and
|
|
21086
|
+
* upload it end-to-end encrypted — recovering interactively from the key limit.
|
|
21087
|
+
* Returns the stored credential; callers render their own success output. Shared
|
|
21088
|
+
* by the `generate push-key` command and the interactive wizard.
|
|
21089
|
+
*/
|
|
21090
|
+
const createApnsKeyViaAppleId = (api, name) => Effect.gen(function* () {
|
|
21091
|
+
const auth = yield* AppleAuth;
|
|
21092
|
+
const session = yield* auth.ensureLoggedIn();
|
|
21093
|
+
const ctx = auth.buildRequestContext(session);
|
|
21094
|
+
const generate = generateAndUploadApnsKeyViaAppleId(api, {
|
|
21095
|
+
context: ctx,
|
|
21096
|
+
appleTeamIdentifier: session.teamId,
|
|
21097
|
+
name
|
|
21098
|
+
});
|
|
21099
|
+
return yield* generate.pipe(Effect.catchTag("ApnsKeyLimitError", () => apnsKeyLimitRecover(ctx).pipe(Effect.flatMap(() => generate))));
|
|
21100
|
+
});
|
|
20974
21101
|
const generateDistributionCertViaAppleIdInteractive = (api, ctx) => Effect.gen(function* () {
|
|
20975
21102
|
yield* Console.log("Generating distribution certificate via Apple ID...");
|
|
20976
21103
|
const generate = generateAndUploadDistributionCertificateViaAppleId(api, { context: ctx });
|
|
@@ -26992,6 +27119,39 @@ const revokeIosDistributionCert = (ctx) => Effect.gen(function* () {
|
|
|
26992
27119
|
["Deleted locally", result.deletedLocally ? "yes" : "no (kept)"]
|
|
26993
27120
|
]);
|
|
26994
27121
|
});
|
|
27122
|
+
const revokeIosPushKey = (ctx) => Effect.gen(function* () {
|
|
27123
|
+
const { items } = yield* ctx.api.applePushKeys.list();
|
|
27124
|
+
if (items.length === 0) return yield* new MissingCredentialsError({
|
|
27125
|
+
message: "No APNs push keys in this account.",
|
|
27126
|
+
hint: "Run 'Add a new push key' to create one first."
|
|
27127
|
+
});
|
|
27128
|
+
const localId = yield* promptSelect("Select a push key to revoke", items.map((key) => ({
|
|
27129
|
+
value: key.id,
|
|
27130
|
+
label: `${key.keyId} (team ${key.appleTeamId})`
|
|
27131
|
+
})));
|
|
27132
|
+
const target = items.find((entry) => entry.id === localId);
|
|
27133
|
+
if (target === void 0) return yield* new MissingCredentialsError({
|
|
27134
|
+
message: `Selected push key ${localId} not found.`,
|
|
27135
|
+
hint: "Re-run and pick again."
|
|
27136
|
+
});
|
|
27137
|
+
const keepLocal = yield* promptConfirm("Keep the key in this account after revoking?", { initialValue: false });
|
|
27138
|
+
const auth = yield* AppleAuth;
|
|
27139
|
+
const session = yield* auth.ensureLoggedIn();
|
|
27140
|
+
yield* Console.log("Logging in to Apple and revoking the push key...");
|
|
27141
|
+
const result = yield* revokeLocalApnsKey(ctx.api, {
|
|
27142
|
+
context: auth.buildRequestContext(session),
|
|
27143
|
+
pushKeyId: target.id,
|
|
27144
|
+
keyId: target.keyId,
|
|
27145
|
+
keepLocal
|
|
27146
|
+
});
|
|
27147
|
+
yield* Console.log("Revoke complete.");
|
|
27148
|
+
yield* printKeyValue([
|
|
27149
|
+
["Local ID", result.localId],
|
|
27150
|
+
["Key ID", result.keyId],
|
|
27151
|
+
["Revoked on Apple", result.revokedOnApple ? "yes" : "no (not present on portal)"],
|
|
27152
|
+
["Deleted locally", result.deletedLocally ? "yes" : "no (kept)"]
|
|
27153
|
+
]);
|
|
27154
|
+
});
|
|
26995
27155
|
|
|
26996
27156
|
//#endregion
|
|
26997
27157
|
//#region src/application/credentials-manager-ios.ts
|
|
@@ -27056,8 +27216,26 @@ const generateNewIosDistributionCert = (ctx) => Effect.gen(function* () {
|
|
|
27056
27216
|
["Apple team", created.appleTeamIdentifier]
|
|
27057
27217
|
]);
|
|
27058
27218
|
});
|
|
27219
|
+
const promptPushKeyMethod = () => promptSelect("How do you want to provide the APNs auth key?", [{
|
|
27220
|
+
value: "apple-id",
|
|
27221
|
+
label: "Create a new key by logging in with your Apple ID (recommended)"
|
|
27222
|
+
}, {
|
|
27223
|
+
value: "upload",
|
|
27224
|
+
label: "Upload a .p8 you already downloaded from the Apple portal"
|
|
27225
|
+
}]);
|
|
27059
27226
|
const addIosPushKey = (ctx) => Effect.gen(function* () {
|
|
27060
|
-
yield*
|
|
27227
|
+
if ((yield* promptPushKeyMethod()) === "apple-id") {
|
|
27228
|
+
const created = yield* createApnsKeyViaAppleId(ctx.api, defaultApnsKeyName());
|
|
27229
|
+
yield* Console.log("APNs push key created and registered.");
|
|
27230
|
+
yield* printKeyValue([
|
|
27231
|
+
["ID", created.id],
|
|
27232
|
+
["Key ID", created.keyId],
|
|
27233
|
+
["Apple team", created.appleTeamIdentifier],
|
|
27234
|
+
["Name", created.name]
|
|
27235
|
+
]);
|
|
27236
|
+
return;
|
|
27237
|
+
}
|
|
27238
|
+
yield* printHuman("Apple does not expose APNs key creation via the public ASC API.");
|
|
27061
27239
|
yield* printHuman(`Create one here, download .p8, then return: ${APPLE_PUSH_KEY_PORTAL_URL$1}`);
|
|
27062
27240
|
const keyId = (yield* promptText("APNs key ID (10 uppercase alphanumeric)")).trim().toUpperCase();
|
|
27063
27241
|
if (!APPLE_TEN_CHARS.test(keyId)) return yield* new CredentialValidationError({ message: `Push key ID "${keyId}" must be 10 uppercase alphanumeric characters.` });
|
|
@@ -27119,7 +27297,12 @@ const setupProjectPushNotifications = (ctx) => Effect.gen(function* () {
|
|
|
27119
27297
|
yield* Console.log(`Push notifications set up: key ${pushKeyId} bound to ${config.bundleIdentifier} (${config.distributionType}).`);
|
|
27120
27298
|
});
|
|
27121
27299
|
const createNewPushKeyForBundle = (ctx, fallbackTeamId) => Effect.gen(function* () {
|
|
27122
|
-
yield*
|
|
27300
|
+
if ((yield* promptPushKeyMethod()) === "apple-id") {
|
|
27301
|
+
const created = yield* createApnsKeyViaAppleId(ctx.api, defaultApnsKeyName());
|
|
27302
|
+
yield* Console.log(`APNs push key ${created.keyId} created.`);
|
|
27303
|
+
return created.id;
|
|
27304
|
+
}
|
|
27305
|
+
yield* printHuman("Apple does not expose APNs key creation via the public ASC API.");
|
|
27123
27306
|
yield* printHuman(`Create one here, download .p8, then return: ${APPLE_PUSH_KEY_PORTAL_URL$1}`);
|
|
27124
27307
|
const rawKeyId = (yield* promptText("APNs key ID (10 uppercase alphanumeric)")).trim().toUpperCase();
|
|
27125
27308
|
if (!APPLE_TEN_CHARS.test(rawKeyId)) return yield* new CredentialValidationError({ message: `Push key ID "${rawKeyId}" must be 10 uppercase alphanumeric characters.` });
|
|
@@ -27206,9 +27389,13 @@ const iosPushKeysMenu = (ctx) => Effect.gen(function* () {
|
|
|
27206
27389
|
value: "bind",
|
|
27207
27390
|
label: "Use an existing push key"
|
|
27208
27391
|
},
|
|
27392
|
+
{
|
|
27393
|
+
value: "revoke",
|
|
27394
|
+
label: "Revoke a push key (Apple Developer Portal)"
|
|
27395
|
+
},
|
|
27209
27396
|
{
|
|
27210
27397
|
value: "remove",
|
|
27211
|
-
label: "Remove a push key"
|
|
27398
|
+
label: "Remove a push key (local only)"
|
|
27212
27399
|
},
|
|
27213
27400
|
{
|
|
27214
27401
|
value: BACK,
|
|
@@ -27219,6 +27406,7 @@ const iosPushKeysMenu = (ctx) => Effect.gen(function* () {
|
|
|
27219
27406
|
if (choice === "setup") yield* safely("set up push notifications", setupProjectPushNotifications(ctx));
|
|
27220
27407
|
else if (choice === "add") yield* safely("add push key", addIosPushKey(ctx));
|
|
27221
27408
|
else if (choice === "bind") yield* safely("bind push key", bindIosPushKey(ctx));
|
|
27409
|
+
else if (choice === "revoke") yield* safely("revoke push key", revokeIosPushKey(ctx));
|
|
27222
27410
|
else if (choice === "remove") yield* safely("remove push key", pickAndDelete(ctx, "push-key", "APNs push key"));
|
|
27223
27411
|
yield* iosPushKeysMenu(ctx);
|
|
27224
27412
|
});
|
|
@@ -28193,6 +28381,129 @@ const downloadCommand = defineCommand({
|
|
|
28193
28381
|
}), { json: "value" })
|
|
28194
28382
|
});
|
|
28195
28383
|
|
|
28384
|
+
//#endregion
|
|
28385
|
+
//#region src/commands/credentials/generate-push-key.ts
|
|
28386
|
+
const PUSH_KEY_EXIT_EXTRAS = {
|
|
28387
|
+
CredentialValidationError: 2,
|
|
28388
|
+
AppleIdGenerateFailedError: 6,
|
|
28389
|
+
ApnsKeyLimitError: 6,
|
|
28390
|
+
AppleAuthError: 4,
|
|
28391
|
+
InteractiveProhibitedError: 4
|
|
28392
|
+
};
|
|
28393
|
+
const APPLE_PUSH_KEY_PORTAL_URL = "https://developer.apple.com/account/resources/authkeys/list";
|
|
28394
|
+
const KEY_ID_PATTERN = /^[A-Z0-9]{10}$/u;
|
|
28395
|
+
const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u;
|
|
28396
|
+
const resolveAppleTeamFromAscKey = (api, ascApiKeyId) => Effect.gen(function* () {
|
|
28397
|
+
if (ascApiKeyId === void 0) return;
|
|
28398
|
+
const teamId = (yield* api.ascApiKeys.list()).items.find((entry) => entry.id === ascApiKeyId)?.appleTeamId;
|
|
28399
|
+
return typeof teamId === "string" ? teamId : void 0;
|
|
28400
|
+
});
|
|
28401
|
+
const validateKeyId = (value) => KEY_ID_PATTERN.test(value) ? Effect.succeed(value) : Effect.fail(new CredentialValidationError({ message: `Push key ID "${value}" must be 10 uppercase alphanumeric characters.` }));
|
|
28402
|
+
const validateAppleTeamId = (value) => APPLE_TEAM_ID_PATTERN.test(value) ? Effect.succeed(value) : Effect.fail(new CredentialValidationError({ message: `Apple Team ID "${value}" must be 10 uppercase alphanumeric characters.` }));
|
|
28403
|
+
const resolvePushKeyInput = (api, args) => Effect.gen(function* () {
|
|
28404
|
+
const derivedTeamId = yield* resolveAppleTeamFromAscKey(api, args["asc-key-id"]);
|
|
28405
|
+
const keyId = yield* validateKeyId((args["key-id"] ?? (yield* promptText("APNs key ID (10 uppercase alphanumeric)"))).trim().toUpperCase());
|
|
28406
|
+
const appleTeamIdentifier = yield* validateAppleTeamId((args["apple-team-id"] ?? derivedTeamId ?? (yield* promptText("Apple Team identifier (10 uppercase alphanumeric)"))).trim().toUpperCase());
|
|
28407
|
+
const p8Path = args.p8 ?? (yield* promptText("Path to the AuthKey_XXXXXXXXXX.p8 file you downloaded"));
|
|
28408
|
+
if (p8Path.trim().length === 0) return yield* new CredentialValidationError({ message: "Missing --p8 path" });
|
|
28409
|
+
return {
|
|
28410
|
+
keyId,
|
|
28411
|
+
appleTeamIdentifier,
|
|
28412
|
+
p8Path,
|
|
28413
|
+
name: args.name ?? keyId
|
|
28414
|
+
};
|
|
28415
|
+
});
|
|
28416
|
+
const resolvePushKeyMethod = (args) => Effect.gen(function* () {
|
|
28417
|
+
if (args.p8 !== void 0 && args.p8.trim().length > 0) return "upload";
|
|
28418
|
+
if (args.method === "upload" || args.method === "apple-id") return args.method;
|
|
28419
|
+
return yield* promptSelect("How do you want to provide the APNs auth key?", [{
|
|
28420
|
+
value: "apple-id",
|
|
28421
|
+
label: "Create a new key by logging in with your Apple ID (recommended)"
|
|
28422
|
+
}, {
|
|
28423
|
+
value: "upload",
|
|
28424
|
+
label: "Upload a .p8 you already downloaded from the Apple portal"
|
|
28425
|
+
}]);
|
|
28426
|
+
});
|
|
28427
|
+
const uploadPushKeyFromFile = (api, args) => Effect.gen(function* () {
|
|
28428
|
+
if (args["skip-portal-hint"] !== true) {
|
|
28429
|
+
yield* printHuman("Apple does not expose APNs key creation via the public ASC API.");
|
|
28430
|
+
yield* printHuman("Create the key here, download the .p8, then come back:");
|
|
28431
|
+
yield* printHuman(` ${APPLE_PUSH_KEY_PORTAL_URL}`);
|
|
28432
|
+
yield* printHuman("");
|
|
28433
|
+
}
|
|
28434
|
+
const resolved = yield* resolvePushKeyInput(api, args);
|
|
28435
|
+
yield* printHuman("Uploading APNs auth key...");
|
|
28436
|
+
const credential = yield* uploadCredential(api, {
|
|
28437
|
+
platform: "ios",
|
|
28438
|
+
type: "push-key",
|
|
28439
|
+
name: resolved.name,
|
|
28440
|
+
filePath: resolved.p8Path,
|
|
28441
|
+
keyId: resolved.keyId,
|
|
28442
|
+
appleTeamIdentifier: resolved.appleTeamIdentifier
|
|
28443
|
+
});
|
|
28444
|
+
yield* printHuman("APNs push key registered.");
|
|
28445
|
+
yield* printHumanKeyValue([
|
|
28446
|
+
["ID", credential.id],
|
|
28447
|
+
["Key ID", resolved.keyId],
|
|
28448
|
+
["Apple team", resolved.appleTeamIdentifier]
|
|
28449
|
+
]);
|
|
28450
|
+
return credential;
|
|
28451
|
+
});
|
|
28452
|
+
const pushKeyCommand$1 = defineCommand({
|
|
28453
|
+
meta: {
|
|
28454
|
+
name: "push-key",
|
|
28455
|
+
description: "Create an APNs auth key (.p8) by logging in with your Apple ID, or upload one you downloaded; the key is end-to-end encrypted before upload"
|
|
28456
|
+
},
|
|
28457
|
+
args: {
|
|
28458
|
+
method: {
|
|
28459
|
+
type: "enum",
|
|
28460
|
+
options: ["apple-id", "upload"],
|
|
28461
|
+
description: "How to obtain the key: 'apple-id' (create via login) or 'upload' (provide --p8)"
|
|
28462
|
+
},
|
|
28463
|
+
"key-id": {
|
|
28464
|
+
type: "string",
|
|
28465
|
+
description: "APNs key ID — upload only (10 uppercase alphanumeric)"
|
|
28466
|
+
},
|
|
28467
|
+
"apple-team-id": {
|
|
28468
|
+
type: "string",
|
|
28469
|
+
description: "Apple Team identifier — upload only"
|
|
28470
|
+
},
|
|
28471
|
+
p8: {
|
|
28472
|
+
type: "string",
|
|
28473
|
+
description: "Path to the AuthKey_XXXXXXXXXX.p8 file (forces upload)"
|
|
28474
|
+
},
|
|
28475
|
+
"asc-key-id": {
|
|
28476
|
+
type: "string",
|
|
28477
|
+
description: "ASC API key ID to derive --apple-team-id automatically (upload only)"
|
|
28478
|
+
},
|
|
28479
|
+
name: {
|
|
28480
|
+
type: "string",
|
|
28481
|
+
description: "Display name (Apple ID: key name; upload: defaults to key ID)"
|
|
28482
|
+
},
|
|
28483
|
+
"skip-portal-hint": {
|
|
28484
|
+
type: "boolean",
|
|
28485
|
+
description: "Skip the Apple Developer portal URL hint (upload only)"
|
|
28486
|
+
}
|
|
28487
|
+
},
|
|
28488
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
28489
|
+
const api = yield* apiClient;
|
|
28490
|
+
if ((yield* resolvePushKeyMethod(args)) === "upload") return yield* uploadPushKeyFromFile(api, args);
|
|
28491
|
+
yield* printHuman("Creating an APNs auth key via your Apple ID...");
|
|
28492
|
+
const created = yield* createApnsKeyViaAppleId(api, args.name ?? defaultApnsKeyName());
|
|
28493
|
+
yield* printHuman("APNs push key created and registered.");
|
|
28494
|
+
yield* printHumanKeyValue([
|
|
28495
|
+
["ID", created.id],
|
|
28496
|
+
["Key ID", created.keyId],
|
|
28497
|
+
["Apple team", created.appleTeamIdentifier],
|
|
28498
|
+
["Name", created.name]
|
|
28499
|
+
]);
|
|
28500
|
+
return created;
|
|
28501
|
+
}), {
|
|
28502
|
+
exits: PUSH_KEY_EXIT_EXTRAS,
|
|
28503
|
+
json: "value"
|
|
28504
|
+
})
|
|
28505
|
+
});
|
|
28506
|
+
|
|
28196
28507
|
//#endregion
|
|
28197
28508
|
//#region src/commands/credentials/generate.ts
|
|
28198
28509
|
const GENERATE_EXIT_EXTRAS = {
|
|
@@ -28401,90 +28712,6 @@ const parseDeviceIds = (raw) => {
|
|
|
28401
28712
|
const ids = raw.split(",").map((id) => id.trim()).filter((id) => id.length > 0);
|
|
28402
28713
|
return ids.length === 0 ? void 0 : ids;
|
|
28403
28714
|
};
|
|
28404
|
-
const APPLE_PUSH_KEY_PORTAL_URL = "https://developer.apple.com/account/resources/authkeys/list";
|
|
28405
|
-
const KEY_ID_PATTERN = /^[A-Z0-9]{10}$/u;
|
|
28406
|
-
const APPLE_TEAM_ID_PATTERN = /^[A-Z0-9]{10}$/u;
|
|
28407
|
-
const resolveAppleTeamFromAscKey = (api, ascApiKeyId) => Effect.gen(function* () {
|
|
28408
|
-
if (ascApiKeyId === void 0) return;
|
|
28409
|
-
const teamId = (yield* api.ascApiKeys.list()).items.find((entry) => entry.id === ascApiKeyId)?.appleTeamId;
|
|
28410
|
-
return typeof teamId === "string" ? teamId : void 0;
|
|
28411
|
-
});
|
|
28412
|
-
const validateKeyId = (value) => KEY_ID_PATTERN.test(value) ? Effect.succeed(value) : Effect.fail(new CredentialValidationError({ message: `Push key ID "${value}" must be 10 uppercase alphanumeric characters.` }));
|
|
28413
|
-
const validateAppleTeamId = (value) => APPLE_TEAM_ID_PATTERN.test(value) ? Effect.succeed(value) : Effect.fail(new CredentialValidationError({ message: `Apple Team ID "${value}" must be 10 uppercase alphanumeric characters.` }));
|
|
28414
|
-
const pushKeyCommand = defineCommand({
|
|
28415
|
-
meta: {
|
|
28416
|
-
name: "push-key",
|
|
28417
|
-
description: "Register an APNs auth key (.p8) — guides you through creating one in the Apple Developer portal, then uploads it"
|
|
28418
|
-
},
|
|
28419
|
-
args: {
|
|
28420
|
-
"key-id": {
|
|
28421
|
-
type: "string",
|
|
28422
|
-
description: "APNs key ID (10 uppercase alphanumeric)"
|
|
28423
|
-
},
|
|
28424
|
-
"apple-team-id": {
|
|
28425
|
-
type: "string",
|
|
28426
|
-
description: "Apple Team identifier"
|
|
28427
|
-
},
|
|
28428
|
-
p8: {
|
|
28429
|
-
type: "string",
|
|
28430
|
-
description: "Path to the AuthKey_XXXXXXXXXX.p8 file"
|
|
28431
|
-
},
|
|
28432
|
-
"asc-key-id": {
|
|
28433
|
-
type: "string",
|
|
28434
|
-
description: "ASC API key ID to derive --apple-team-id automatically"
|
|
28435
|
-
},
|
|
28436
|
-
name: {
|
|
28437
|
-
type: "string",
|
|
28438
|
-
description: "Display name (defaults to the key ID)"
|
|
28439
|
-
},
|
|
28440
|
-
"skip-portal-hint": {
|
|
28441
|
-
type: "boolean",
|
|
28442
|
-
description: "Skip the Apple Developer portal URL hint (already created the key)"
|
|
28443
|
-
}
|
|
28444
|
-
},
|
|
28445
|
-
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
28446
|
-
const api = yield* apiClient;
|
|
28447
|
-
if (args["skip-portal-hint"] !== true) {
|
|
28448
|
-
yield* printHuman("Apple does not expose APNs key creation via the public ASC API.");
|
|
28449
|
-
yield* printHuman("Create the key here, download the .p8, then come back:");
|
|
28450
|
-
yield* printHuman(` ${APPLE_PUSH_KEY_PORTAL_URL}`);
|
|
28451
|
-
yield* printHuman("");
|
|
28452
|
-
}
|
|
28453
|
-
const resolved = yield* resolvePushKeyInput(api, args);
|
|
28454
|
-
yield* printHuman("Uploading APNs auth key...");
|
|
28455
|
-
const credential = yield* uploadCredential(api, {
|
|
28456
|
-
platform: "ios",
|
|
28457
|
-
type: "push-key",
|
|
28458
|
-
name: resolved.name,
|
|
28459
|
-
filePath: resolved.p8Path,
|
|
28460
|
-
keyId: resolved.keyId,
|
|
28461
|
-
appleTeamIdentifier: resolved.appleTeamIdentifier
|
|
28462
|
-
});
|
|
28463
|
-
yield* printHuman("APNs push key registered.");
|
|
28464
|
-
yield* printHumanKeyValue([
|
|
28465
|
-
["ID", credential.id],
|
|
28466
|
-
["Key ID", resolved.keyId],
|
|
28467
|
-
["Apple team", resolved.appleTeamIdentifier]
|
|
28468
|
-
]);
|
|
28469
|
-
return credential;
|
|
28470
|
-
}), {
|
|
28471
|
-
exits: GENERATE_EXIT_EXTRAS,
|
|
28472
|
-
json: "value"
|
|
28473
|
-
})
|
|
28474
|
-
});
|
|
28475
|
-
const resolvePushKeyInput = (api, args) => Effect.gen(function* () {
|
|
28476
|
-
const derivedTeamId = yield* resolveAppleTeamFromAscKey(api, args["asc-key-id"]);
|
|
28477
|
-
const keyId = yield* validateKeyId((args["key-id"] ?? (yield* promptText("APNs key ID (10 uppercase alphanumeric)"))).trim().toUpperCase());
|
|
28478
|
-
const appleTeamIdentifier = yield* validateAppleTeamId((args["apple-team-id"] ?? derivedTeamId ?? (yield* promptText("Apple Team identifier (10 uppercase alphanumeric)"))).trim().toUpperCase());
|
|
28479
|
-
const p8Path = args.p8 ?? (yield* promptText("Path to the AuthKey_XXXXXXXXXX.p8 file you downloaded"));
|
|
28480
|
-
if (p8Path.trim().length === 0) return yield* new CredentialValidationError({ message: "Missing --p8 path" });
|
|
28481
|
-
return {
|
|
28482
|
-
keyId,
|
|
28483
|
-
appleTeamIdentifier,
|
|
28484
|
-
p8Path,
|
|
28485
|
-
name: args.name ?? keyId
|
|
28486
|
-
};
|
|
28487
|
-
});
|
|
28488
28715
|
const GSA_FIREBASE_URL = "https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk";
|
|
28489
28716
|
const GSA_GCP_URL = "https://console.cloud.google.com/iam-admin/serviceaccounts";
|
|
28490
28717
|
const gsaKeyCommand = defineCommand({
|
|
@@ -28551,7 +28778,7 @@ const generateCommand$1 = defineCommand({
|
|
|
28551
28778
|
keystore: keystoreCommand,
|
|
28552
28779
|
"distribution-certificate": distributionCertificateCommand$1,
|
|
28553
28780
|
"provisioning-profile": provisioningProfileCommand,
|
|
28554
|
-
"push-key": pushKeyCommand,
|
|
28781
|
+
"push-key": pushKeyCommand$1,
|
|
28555
28782
|
"gsa-key": gsaKeyCommand
|
|
28556
28783
|
}
|
|
28557
28784
|
});
|
|
@@ -28956,7 +29183,10 @@ const resolveType = (raw, available) => Effect.gen(function* () {
|
|
|
28956
29183
|
//#region src/commands/credentials/revoke.ts
|
|
28957
29184
|
const REVOKE_EXIT_EXTRAS = {
|
|
28958
29185
|
CredentialValidationError: 2,
|
|
28959
|
-
GenerateFailedError: 6
|
|
29186
|
+
GenerateFailedError: 6,
|
|
29187
|
+
AppleIdGenerateFailedError: 6,
|
|
29188
|
+
AppleAuthError: 4,
|
|
29189
|
+
InteractiveProhibitedError: 4
|
|
28960
29190
|
};
|
|
28961
29191
|
const resolveAscKeyId = (api, raw) => Effect.gen(function* () {
|
|
28962
29192
|
if (raw !== void 0 && raw.length > 0) return raw;
|
|
@@ -29011,12 +29241,74 @@ const distributionCertificateCommand = defineCommand({
|
|
|
29011
29241
|
json: "value"
|
|
29012
29242
|
})
|
|
29013
29243
|
});
|
|
29244
|
+
const resolvePushKeyTarget = (api, idArg) => Effect.gen(function* () {
|
|
29245
|
+
const { items } = yield* api.applePushKeys.list();
|
|
29246
|
+
if (items.length === 0) return yield* new CredentialValidationError({ message: "No APNs push keys stored. Nothing to revoke." });
|
|
29247
|
+
if (idArg !== void 0 && idArg.length > 0) {
|
|
29248
|
+
const match = items.find((entry) => entry.id === idArg);
|
|
29249
|
+
if (match === void 0) return yield* new CredentialValidationError({ message: `Push key ${idArg} not found.` });
|
|
29250
|
+
return match;
|
|
29251
|
+
}
|
|
29252
|
+
if (items.length === 1) {
|
|
29253
|
+
const [only] = items;
|
|
29254
|
+
if (only !== void 0) return only;
|
|
29255
|
+
}
|
|
29256
|
+
const chosen = yield* promptSelect("Select a push key to revoke", items.map((entry) => ({
|
|
29257
|
+
value: entry.id,
|
|
29258
|
+
label: `${entry.keyId} (team ${entry.appleTeamId})`
|
|
29259
|
+
})));
|
|
29260
|
+
const match = items.find((entry) => entry.id === chosen);
|
|
29261
|
+
if (match === void 0) return yield* new CredentialValidationError({ message: `Selected push key ${chosen} not found after listing.` });
|
|
29262
|
+
return match;
|
|
29263
|
+
});
|
|
29264
|
+
const pushKeyCommand = defineCommand({
|
|
29265
|
+
meta: {
|
|
29266
|
+
name: "push-key",
|
|
29267
|
+
description: "Revoke an APNs auth key on the Apple Developer Portal (via Apple ID login) and delete it from this account"
|
|
29268
|
+
},
|
|
29269
|
+
args: {
|
|
29270
|
+
id: {
|
|
29271
|
+
type: "string",
|
|
29272
|
+
description: "Local push key ID (prompts if omitted)"
|
|
29273
|
+
},
|
|
29274
|
+
"keep-local": {
|
|
29275
|
+
type: "boolean",
|
|
29276
|
+
description: "Revoke on Apple but keep the credential in this account"
|
|
29277
|
+
}
|
|
29278
|
+
},
|
|
29279
|
+
run: async ({ args }) => runEffect(Effect.gen(function* () {
|
|
29280
|
+
const api = yield* apiClient;
|
|
29281
|
+
const target = yield* resolvePushKeyTarget(api, args.id);
|
|
29282
|
+
const auth = yield* AppleAuth;
|
|
29283
|
+
const session = yield* auth.ensureLoggedIn();
|
|
29284
|
+
const result = yield* revokeLocalApnsKey(api, {
|
|
29285
|
+
context: auth.buildRequestContext(session),
|
|
29286
|
+
pushKeyId: target.id,
|
|
29287
|
+
keyId: target.keyId,
|
|
29288
|
+
keepLocal: args["keep-local"] ?? false
|
|
29289
|
+
});
|
|
29290
|
+
yield* printHuman("APNs push key revoke complete.");
|
|
29291
|
+
yield* printHumanKeyValue([
|
|
29292
|
+
["Local ID", result.localId],
|
|
29293
|
+
["Key ID", result.keyId],
|
|
29294
|
+
["Revoked on Apple", result.revokedOnApple ? "yes" : "no (not present on portal)"],
|
|
29295
|
+
["Deleted locally", result.deletedLocally ? "yes" : "no (--keep-local)"]
|
|
29296
|
+
]);
|
|
29297
|
+
return result;
|
|
29298
|
+
}), {
|
|
29299
|
+
exits: REVOKE_EXIT_EXTRAS,
|
|
29300
|
+
json: "value"
|
|
29301
|
+
})
|
|
29302
|
+
});
|
|
29014
29303
|
const revokeCommand = defineCommand({
|
|
29015
29304
|
meta: {
|
|
29016
29305
|
name: "revoke",
|
|
29017
29306
|
description: "Revoke credentials on the upstream provider"
|
|
29018
29307
|
},
|
|
29019
|
-
subCommands: {
|
|
29308
|
+
subCommands: {
|
|
29309
|
+
"distribution-certificate": distributionCertificateCommand,
|
|
29310
|
+
"push-key": pushKeyCommand
|
|
29311
|
+
}
|
|
29020
29312
|
});
|
|
29021
29313
|
|
|
29022
29314
|
//#endregion
|