eas-cli 20.1.0 → 20.3.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.
Files changed (46) hide show
  1. package/README.md +223 -120
  2. package/build/commandUtils/posthog.d.ts +4 -0
  3. package/build/commandUtils/posthog.js +23 -0
  4. package/build/commands/account/audit.d.ts +17 -0
  5. package/build/commands/account/audit.js +112 -0
  6. package/build/commands/integrations/posthog/connect.d.ts +27 -0
  7. package/build/commands/integrations/posthog/connect.js +432 -0
  8. package/build/commands/integrations/posthog/dashboard.d.ts +13 -0
  9. package/build/commands/integrations/posthog/dashboard.js +66 -0
  10. package/build/commands/integrations/posthog/disconnect.d.ts +14 -0
  11. package/build/commands/integrations/posthog/disconnect.js +80 -0
  12. package/build/commands/simulator/start.d.ts +1 -0
  13. package/build/commands/simulator/start.js +5 -0
  14. package/build/commands/update/rollback.d.ts +11 -0
  15. package/build/commands/update/rollback.js +117 -14
  16. package/build/commands/update/view.d.ts +7 -0
  17. package/build/commands/update/view.js +30 -3
  18. package/build/credentials/ios/actions/AscApiKeyUtils.d.ts +20 -0
  19. package/build/credentials/ios/actions/AscApiKeyUtils.js +64 -0
  20. package/build/credentials/ios/actions/ConfigureProvisioningProfile.js +2 -4
  21. package/build/credentials/ios/actions/CreateProvisioningProfile.js +2 -4
  22. package/build/credentials/ios/actions/SetUpAdhocProvisioningProfile.js +3 -20
  23. package/build/credentials/ios/actions/SetUpProvisioningProfile.d.ts +10 -0
  24. package/build/credentials/ios/actions/SetUpProvisioningProfile.js +39 -5
  25. package/build/credentials/ios/appstore/resolveCredentials.d.ts +1 -0
  26. package/build/credentials/ios/appstore/resolveCredentials.js +1 -0
  27. package/build/graphql/generated.d.ts +611 -30
  28. package/build/graphql/generated.js +29 -5
  29. package/build/graphql/mutations/PostHogMutation.d.ts +8 -0
  30. package/build/graphql/mutations/PostHogMutation.js +55 -0
  31. package/build/graphql/queries/AuditLogQuery.d.ts +6 -0
  32. package/build/graphql/queries/AuditLogQuery.js +57 -0
  33. package/build/graphql/queries/DeviceRunSessionQuery.js +1 -0
  34. package/build/graphql/queries/PostHogQuery.d.ts +6 -0
  35. package/build/graphql/queries/PostHogQuery.js +49 -0
  36. package/build/graphql/queries/UpdateQuery.d.ts +2 -1
  37. package/build/graphql/queries/UpdateQuery.js +52 -0
  38. package/build/graphql/types/AuditLog.d.ts +1 -0
  39. package/build/graphql/types/AuditLog.js +18 -0
  40. package/build/graphql/types/PostHogConnection.d.ts +7 -0
  41. package/build/graphql/types/PostHogConnection.js +30 -0
  42. package/build/simulator/utils.js +28 -5
  43. package/build/user/SessionManager.d.ts +1 -22
  44. package/build/user/SessionManager.js +7 -89
  45. package/oclif.manifest.json +920 -488
  46. package/package.json +5 -2
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const better_opn_1 = tslib_1.__importDefault(require("better-opn"));
5
+ const EasCommand_1 = tslib_1.__importDefault(require("../../../commandUtils/EasCommand"));
6
+ const flags_1 = require("../../../commandUtils/flags");
7
+ const posthog_1 = require("../../../commandUtils/posthog");
8
+ const PostHogQuery_1 = require("../../../graphql/queries/PostHogQuery");
9
+ const log_1 = tslib_1.__importDefault(require("../../../log"));
10
+ const ora_1 = require("../../../ora");
11
+ const json_1 = require("../../../utils/json");
12
+ class IntegrationsPostHogDashboard extends EasCommand_1.default {
13
+ static description = 'open the PostHog dashboard for the linked PostHog project';
14
+ static flags = {
15
+ ...flags_1.EasNonInteractiveAndJsonFlags,
16
+ };
17
+ static contextDefinition = {
18
+ ...this.ContextOptions.ProjectConfig,
19
+ };
20
+ async runAsync() {
21
+ const { flags } = await this.parse(IntegrationsPostHogDashboard);
22
+ const { json: jsonFlag, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
23
+ if (jsonFlag) {
24
+ (0, json_1.enableJsonOutput)();
25
+ }
26
+ const { privateProjectConfig: { projectId, exp }, loggedIn: { graphqlClient }, } = await this.getContextAsync(IntegrationsPostHogDashboard, {
27
+ nonInteractive,
28
+ withServerSideEnvironment: null,
29
+ });
30
+ const posthogProject = await PostHogQuery_1.PostHogQuery.getPostHogProjectByAppIdAsync(graphqlClient, projectId);
31
+ if (!posthogProject) {
32
+ if (jsonFlag) {
33
+ (0, json_1.printJsonOnlyOutput)({ dashboardUrl: null });
34
+ }
35
+ else {
36
+ (0, posthog_1.logNoPostHogProject)(exp.slug);
37
+ }
38
+ return;
39
+ }
40
+ const dashboardUrl = (0, posthog_1.getPostHogProjectDashboardUrl)(posthogProject);
41
+ if (jsonFlag) {
42
+ (0, json_1.printJsonOnlyOutput)({ dashboardUrl });
43
+ return;
44
+ }
45
+ if (nonInteractive) {
46
+ log_1.default.log(dashboardUrl);
47
+ return;
48
+ }
49
+ const failedMessage = `Unable to open a web browser. PostHog dashboard is available at: ${dashboardUrl}`;
50
+ const spinner = (0, ora_1.ora)(`Opening ${dashboardUrl}`).start();
51
+ try {
52
+ const opened = await (0, better_opn_1.default)(dashboardUrl);
53
+ if (opened) {
54
+ spinner.succeed(`Opened ${dashboardUrl}`);
55
+ }
56
+ else {
57
+ spinner.fail(failedMessage);
58
+ }
59
+ }
60
+ catch (error) {
61
+ spinner.fail(failedMessage);
62
+ throw error;
63
+ }
64
+ }
65
+ }
66
+ exports.default = IntegrationsPostHogDashboard;
@@ -0,0 +1,14 @@
1
+ import EasCommand from '../../../commandUtils/EasCommand';
2
+ export default class IntegrationsPostHogDisconnect extends EasCommand {
3
+ static description: string;
4
+ static flags: {
5
+ yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
6
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
8
+ };
9
+ static contextDefinition: {
10
+ loggedIn: import("../../../commandUtils/context/LoggedInContextField").default;
11
+ privateProjectConfig: import("../../../commandUtils/context/PrivateProjectConfigContextField").PrivateProjectConfigContextField;
12
+ };
13
+ runAsync(): Promise<void>;
14
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const core_1 = require("@oclif/core");
5
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
6
+ const EasCommand_1 = tslib_1.__importDefault(require("../../../commandUtils/EasCommand"));
7
+ const flags_1 = require("../../../commandUtils/flags");
8
+ const posthog_1 = require("../../../commandUtils/posthog");
9
+ const PostHogMutation_1 = require("../../../graphql/mutations/PostHogMutation");
10
+ const PostHogQuery_1 = require("../../../graphql/queries/PostHogQuery");
11
+ const log_1 = tslib_1.__importDefault(require("../../../log"));
12
+ const ora_1 = require("../../../ora");
13
+ const prompts_1 = require("../../../prompts");
14
+ const json_1 = require("../../../utils/json");
15
+ class IntegrationsPostHogDisconnect extends EasCommand_1.default {
16
+ static description = 'remove the PostHog project link for the current Expo app from EAS servers';
17
+ static flags = {
18
+ ...flags_1.EasNonInteractiveAndJsonFlags,
19
+ yes: core_1.Flags.boolean({
20
+ char: 'y',
21
+ description: 'Skip confirmation prompt',
22
+ default: false,
23
+ }),
24
+ };
25
+ static contextDefinition = {
26
+ ...this.ContextOptions.ProjectConfig,
27
+ };
28
+ async runAsync() {
29
+ const { flags } = await this.parse(IntegrationsPostHogDisconnect);
30
+ const { yes } = flags;
31
+ const { json: jsonFlag, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
32
+ if (jsonFlag) {
33
+ (0, json_1.enableJsonOutput)();
34
+ }
35
+ const { privateProjectConfig: { projectId, exp }, loggedIn: { graphqlClient }, } = await this.getContextAsync(IntegrationsPostHogDisconnect, {
36
+ nonInteractive,
37
+ withServerSideEnvironment: null,
38
+ });
39
+ const posthogProject = await PostHogQuery_1.PostHogQuery.getPostHogProjectByAppIdAsync(graphqlClient, projectId);
40
+ if (!posthogProject) {
41
+ if (jsonFlag) {
42
+ (0, json_1.printJsonOnlyOutput)({ id: null });
43
+ }
44
+ else {
45
+ (0, posthog_1.logNoPostHogProject)(exp.slug);
46
+ }
47
+ return;
48
+ }
49
+ if (!jsonFlag) {
50
+ log_1.default.addNewLineIfNone();
51
+ log_1.default.log((0, posthog_1.formatPostHogProject)(posthogProject));
52
+ log_1.default.newLine();
53
+ }
54
+ if (!nonInteractive && !yes) {
55
+ const confirmed = await (0, prompts_1.confirmAsync)({
56
+ message: 'Remove this PostHog project link from EAS servers? This does not delete the project on PostHog.',
57
+ });
58
+ if (!confirmed) {
59
+ log_1.default.warn('Canceled removal of the PostHog project link.');
60
+ return;
61
+ }
62
+ }
63
+ else if (!jsonFlag) {
64
+ log_1.default.warn('Removing the PostHog project link from EAS servers. This does not delete the project on PostHog.');
65
+ }
66
+ const spinner = jsonFlag ? null : (0, ora_1.ora)('Removing PostHog project link').start();
67
+ try {
68
+ await PostHogMutation_1.PostHogMutation.deletePostHogProjectAsync(graphqlClient, posthogProject.id);
69
+ spinner?.succeed(`Removed PostHog project ${chalk_1.default.bold(posthogProject.posthogProjectName)} from EAS servers`);
70
+ }
71
+ catch (error) {
72
+ spinner?.fail('Failed to remove PostHog project link');
73
+ throw error;
74
+ }
75
+ if (jsonFlag) {
76
+ (0, json_1.printJsonOnlyOutput)({ id: posthogProject.id, name: posthogProject.posthogProjectName });
77
+ }
78
+ }
79
+ }
80
+ exports.default = IntegrationsPostHogDisconnect;
@@ -8,6 +8,7 @@ export default class SimulatorStart extends EasCommand {
8
8
  platform: import("@oclif/core/lib/interfaces").OptionFlag<"android" | "ios", import("@oclif/core/lib/interfaces").CustomOptions>;
9
9
  type: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
10
10
  'package-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ 'max-duration-minutes': import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
12
  force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
13
  'out-config-type': import("@oclif/core/lib/interfaces").OptionFlag<"env" | "dotenv", import("@oclif/core/lib/interfaces").CustomOptions>;
13
14
  };
@@ -38,6 +38,10 @@ class SimulatorStart extends EasCommand_1.default {
38
38
  'package-version': core_1.Flags.string({
39
39
  description: 'Version of the package backing the device run session (e.g. "0.1.3-alpha.3"). Defaults to "latest" when omitted.',
40
40
  }),
41
+ 'max-duration-minutes': core_1.Flags.integer({
42
+ description: 'Maximum duration of the device run session in minutes before it is automatically stopped. Only customizable on paid plans. Defaults to a value derived from the job run priority when omitted.',
43
+ min: 0,
44
+ }),
41
45
  force: core_1.Flags.boolean({
42
46
  description: '[default: true] Create a new device session even when an existing simulator session is present in the environment.',
43
47
  default: true,
@@ -85,6 +89,7 @@ class SimulatorStart extends EasCommand_1.default {
85
89
  platform,
86
90
  type: utils_1.DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE[flags.type],
87
91
  packageVersion: flags['package-version'],
92
+ maxRunTimeMinutes: flags['max-duration-minutes'],
88
93
  });
89
94
  deviceRunSessionId = session.id;
90
95
  const jobRunId = (0, nullthrows_1.default)(session.turtleJobRun?.id, 'Expected device run session to start');
@@ -1,8 +1,19 @@
1
1
  import EasCommand from '../../commandUtils/EasCommand';
2
2
  export default class UpdateRollback extends EasCommand {
3
3
  static description: string;
4
+ static args: {
5
+ groupId: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
6
+ };
4
7
  static flags: {
8
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ message: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ platform: import("@oclif/core/lib/interfaces").OptionFlag<"android" | "ios" | "all", import("@oclif/core/lib/interfaces").CustomOptions>;
5
12
  'private-key-path': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
6
13
  };
14
+ static contextDefinition: {
15
+ loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
16
+ privateProjectConfig: import("../../commandUtils/context/PrivateProjectConfigContextField").PrivateProjectConfigContextField;
17
+ };
7
18
  runAsync(): Promise<void>;
8
19
  }
@@ -5,35 +5,138 @@ const core_1 = require("@oclif/core");
5
5
  const republish_1 = tslib_1.__importDefault(require("./republish"));
6
6
  const roll_back_to_embedded_1 = tslib_1.__importDefault(require("./roll-back-to-embedded"));
7
7
  const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
8
+ const flags_1 = require("../../commandUtils/flags");
9
+ const UpdateQuery_1 = require("../../graphql/queries/UpdateQuery");
8
10
  const prompts_1 = require("../../prompts");
11
+ const defaultRollbackPlatforms = ['android', 'ios'];
9
12
  class UpdateRollback extends EasCommand_1.default {
10
- static description = 'Roll back to an embedded update or an existing update. Users wishing to run this command non-interactively should instead execute "eas update:republish" or "eas update:roll-back-to-embedded".';
13
+ static description = 'roll back to an embedded update or an existing update';
14
+ static args = {
15
+ groupId: core_1.Args.string({
16
+ description: 'The ID of the update group to roll back. Must be the latest update for its branch and runtime version. The update group published before it is republished; if there is none, a roll back to the embedded update is published. Required in non-interactive mode.',
17
+ required: false,
18
+ }),
19
+ };
11
20
  static flags = {
21
+ message: core_1.Flags.string({
22
+ char: 'm',
23
+ description: 'Short message describing the rollback update',
24
+ required: false,
25
+ }),
26
+ platform: core_1.Flags.option({
27
+ char: 'p',
28
+ options: [...defaultRollbackPlatforms, 'all'],
29
+ default: 'all',
30
+ required: false,
31
+ })(),
12
32
  'private-key-path': core_1.Flags.string({
13
33
  description: `File containing the PEM-encoded private key corresponding to the certificate in expo-updates' configuration. Defaults to a file named "private-key.pem" in the certificate's directory. Only relevant if you are using code signing: https://docs.expo.dev/eas-update/code-signing/`,
14
34
  required: false,
15
35
  }),
36
+ ...flags_1.EasNonInteractiveAndJsonFlags,
37
+ };
38
+ static contextDefinition = {
39
+ ...this.ContextOptions.ProjectConfig,
40
+ ...this.ContextOptions.LoggedIn,
16
41
  };
17
42
  async runAsync() {
18
- const { flags } = await this.parse(UpdateRollback);
19
- const { choice } = await (0, prompts_1.promptAsync)({
20
- type: 'select',
21
- message: 'Which type of update would you like to roll back to?',
22
- name: 'choice',
23
- choices: [
24
- { title: 'Published Update', value: 'published' },
25
- { title: 'Embedded Update', value: 'embedded' },
26
- ],
27
- });
43
+ const { args, flags } = await this.parse(UpdateRollback);
44
+ const { json, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
45
+ const groupId = args.groupId;
46
+ const platform = flags.platform;
47
+ const messageArg = flags.message;
28
48
  const privateKeyPathArg = flags['private-key-path']
29
49
  ? ['--private-key-path', flags['private-key-path']]
30
50
  : [];
31
- if (choice === 'published') {
32
- await republish_1.default.run(privateKeyPathArg);
51
+ if (!groupId) {
52
+ if (nonInteractive) {
53
+ throw new Error('The update group ID argument is required in non-interactive mode.');
54
+ }
55
+ const { choice } = await (0, prompts_1.promptAsync)({
56
+ type: 'select',
57
+ message: 'Which type of update would you like to roll back to?',
58
+ name: 'choice',
59
+ choices: [
60
+ { title: 'Published Update', value: 'published' },
61
+ { title: 'Embedded Update', value: 'embedded' },
62
+ ],
63
+ });
64
+ if (choice === 'published') {
65
+ await republish_1.default.run(privateKeyPathArg);
66
+ }
67
+ else {
68
+ await roll_back_to_embedded_1.default.run(privateKeyPathArg);
69
+ }
70
+ return;
71
+ }
72
+ const { loggedIn: { graphqlClient }, privateProjectConfig: { projectId }, } = await this.getContextAsync(UpdateRollback, {
73
+ nonInteractive,
74
+ withServerSideEnvironment: null,
75
+ });
76
+ const sourceGroup = await getSourceUpdateGroupAsync(graphqlClient, groupId);
77
+ const previousGroup = await getPreviousUpdateGroupAsync(graphqlClient, projectId, sourceGroup);
78
+ const commonArgs = [
79
+ '--non-interactive',
80
+ '--platform',
81
+ platform,
82
+ ...privateKeyPathArg,
83
+ ...(json ? ['--json'] : []),
84
+ ];
85
+ if (previousGroup) {
86
+ const message = messageArg ??
87
+ `Roll back to "${previousGroup.message ?? ''}" (group: ${previousGroup.groupId})`;
88
+ await republish_1.default.run([
89
+ '--group',
90
+ previousGroup.groupId,
91
+ '--message',
92
+ message,
93
+ ...commonArgs,
94
+ ]);
33
95
  }
34
96
  else {
35
- await roll_back_to_embedded_1.default.run(privateKeyPathArg);
97
+ const message = messageArg ?? 'Roll back to embedded';
98
+ await roll_back_to_embedded_1.default.run([
99
+ '--branch',
100
+ sourceGroup.branchName,
101
+ '--runtime-version',
102
+ sourceGroup.runtimeVersion,
103
+ '--message',
104
+ message,
105
+ ...commonArgs,
106
+ ]);
36
107
  }
37
108
  }
38
109
  }
39
110
  exports.default = UpdateRollback;
111
+ async function getSourceUpdateGroupAsync(graphqlClient, groupId) {
112
+ // viewUpdateGroupAsync throws if no updates are found for the group ID.
113
+ const updateGroup = await UpdateQuery_1.UpdateQuery.viewUpdateGroupAsync(graphqlClient, { groupId });
114
+ const arbitraryUpdate = updateGroup[0];
115
+ return {
116
+ groupId,
117
+ branchName: arbitraryUpdate.branch.name,
118
+ runtimeVersion: arbitraryUpdate.runtimeVersion,
119
+ };
120
+ }
121
+ async function getPreviousUpdateGroupAsync(graphqlClient, projectId, sourceGroup) {
122
+ // Clients on a given runtime version are served the latest update group on the branch,
123
+ // so a rollback is only meaningful when the source group is that latest group. Fetch the
124
+ // two most recent groups for the runtime version (returned most-recent-first): the first
125
+ // must be the source group, and the second (if any) is the update to roll back to.
126
+ const latestGroups = await UpdateQuery_1.UpdateQuery.viewUpdateGroupsPaginatedOnBranchAsync(graphqlClient, {
127
+ appId: projectId,
128
+ branchName: sourceGroup.branchName,
129
+ first: 2,
130
+ filter: { runtimeVersions: [sourceGroup.runtimeVersion] },
131
+ });
132
+ const latestGroup = latestGroups[0];
133
+ if (!latestGroup?.length || latestGroup[0].group !== sourceGroup.groupId) {
134
+ throw new Error(`Update group "${sourceGroup.groupId}" is not the latest update on branch "${sourceGroup.branchName}" for runtime version "${sourceGroup.runtimeVersion}"${latestGroup?.length ? ` (the latest is "${latestGroup[0].group}")` : ''}. Only the latest update can be rolled back.`);
135
+ }
136
+ // Source group is the only update for this runtime version: roll back to embedded.
137
+ const previousGroup = latestGroups[1];
138
+ if (!previousGroup?.length) {
139
+ return null;
140
+ }
141
+ return { groupId: previousGroup[0].group, message: previousGroup[0].message ?? null };
142
+ }
@@ -15,4 +15,11 @@ export default class UpdateView extends EasCommand {
15
15
  loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
16
16
  };
17
17
  runAsync(): Promise<void>;
18
+ /**
19
+ * Resolves the provided ID into an update group and its updates. The ID may be either an
20
+ * update group ID or the ID of a single platform-specific update. We first try to look it up
21
+ * as a group; if no updates are found, we fall back to resolving it as a platform-specific
22
+ * update and then fetch the group that update belongs to.
23
+ */
24
+ private static resolveUpdateGroupAsync;
18
25
  }
@@ -17,7 +17,7 @@ class UpdateView extends EasCommand_1.default {
17
17
  static args = {
18
18
  groupId: core_1.Args.string({
19
19
  required: true,
20
- description: 'The ID of an update group.',
20
+ description: 'The ID of an update group, or the ID of a platform-specific update.',
21
21
  }),
22
22
  };
23
23
  static flags = {
@@ -44,7 +44,7 @@ class UpdateView extends EasCommand_1.default {
44
44
  ...this.ContextOptions.LoggedIn,
45
45
  };
46
46
  async runAsync() {
47
- const { args: { groupId }, flags: { json: jsonFlag, insights: insightsFlag, days, start, end }, } = await this.parse(UpdateView);
47
+ const { args: { groupId: idArg }, flags: { json: jsonFlag, insights: insightsFlag, days, start, end }, } = await this.parse(UpdateView);
48
48
  if (!insightsFlag && (days !== undefined || start !== undefined || end !== undefined)) {
49
49
  throw new Error('--days, --start, and --end can only be used with --insights.');
50
50
  }
@@ -52,7 +52,7 @@ class UpdateView extends EasCommand_1.default {
52
52
  if (jsonFlag) {
53
53
  (0, json_1.enableJsonOutput)();
54
54
  }
55
- const updatesByGroup = await UpdateQuery_1.UpdateQuery.viewUpdateGroupAsync(graphqlClient, { groupId });
55
+ const { groupId, updatesByGroup } = await UpdateView.resolveUpdateGroupAsync(graphqlClient, idArg);
56
56
  let insightsSummary = null;
57
57
  if (insightsFlag) {
58
58
  const { daysBack, startTime, endTime } = (0, timeRange_1.resolveInsightsTimeRange)({ days, start, end });
@@ -84,5 +84,32 @@ class UpdateView extends EasCommand_1.default {
84
84
  }
85
85
  }
86
86
  }
87
+ /**
88
+ * Resolves the provided ID into an update group and its updates. The ID may be either an
89
+ * update group ID or the ID of a single platform-specific update. We first try to look it up
90
+ * as a group; if no updates are found, we fall back to resolving it as a platform-specific
91
+ * update and then fetch the group that update belongs to.
92
+ */
93
+ static async resolveUpdateGroupAsync(graphqlClient, id) {
94
+ try {
95
+ const updatesByGroup = await UpdateQuery_1.UpdateQuery.viewUpdateGroupAsync(graphqlClient, { groupId: id });
96
+ return { groupId: id, updatesByGroup };
97
+ }
98
+ catch (groupError) {
99
+ let update;
100
+ try {
101
+ update = await UpdateQuery_1.UpdateQuery.viewByUpdateAsync(graphqlClient, { updateId: id });
102
+ }
103
+ catch {
104
+ // The ID is neither a valid update group nor a valid platform-specific update; surface
105
+ // the original group lookup error since the group ID is the primary input.
106
+ throw groupError;
107
+ }
108
+ const updatesByGroup = await UpdateQuery_1.UpdateQuery.viewUpdateGroupAsync(graphqlClient, {
109
+ groupId: update.group,
110
+ });
111
+ return { groupId: update.group, updatesByGroup };
112
+ }
113
+ }
87
114
  }
88
115
  exports.default = UpdateView;
@@ -1,6 +1,9 @@
1
+ import { ExpoGraphqlClient } from '../../../commandUtils/context/contextUtils/createGraphqlClient';
1
2
  import { AccountFragment, AppStoreConnectApiKeyFragment } from '../../../graphql/generated';
2
3
  import { CredentialsContext } from '../../context';
4
+ import { AppLookupParams } from '../api/graphql/types/AppLookupParams';
3
5
  import { AscApiKey } from '../appstore/Credentials.types';
6
+ import { AppleTeamType } from '../appstore/authenticateTypes';
4
7
  import { AscApiKeyPath, MinimalAscApiKey } from '../credentials';
5
8
  export declare enum AppStoreApiKeyPurpose {
6
9
  SUBMISSION_SERVICE = "EAS Submit",
@@ -19,3 +22,20 @@ export declare function selectAscApiKeysFromAccountAsync(ctx: CredentialsContext
19
22
  }): Promise<AppStoreConnectApiKeyFragment | null>;
20
23
  export declare function sortAscApiKeysByUpdatedAtDesc(keys: AppStoreConnectApiKeyFragment[]): AppStoreConnectApiKeyFragment[];
21
24
  export declare function formatAscApiKey(ascApiKey: AppStoreConnectApiKeyFragment): string;
25
+ export declare function resolveAscApiKeyForAppCredentialsAsync({ graphqlClient, app, }: {
26
+ graphqlClient: ExpoGraphqlClient;
27
+ app: AppLookupParams;
28
+ }): Promise<{
29
+ ascApiKey: MinimalAscApiKey;
30
+ teamId?: string;
31
+ teamName?: string;
32
+ } | null>;
33
+ /**
34
+ * Best-effort helper that populates `ctx.appStore.authCtx` in non-interactive mode
35
+ * by loading an App Store Connect API key (from env vars, or by fetching the app's
36
+ * stored key from the www GraphQL API).
37
+ *
38
+ * Returns true if `ctx.appStore.authCtx` is set after the call, false otherwise.
39
+ * Never throws.
40
+ */
41
+ export declare function tryAuthenticateAppStoreWithEasAscApiKeyAsync(ctx: CredentialsContext, app: AppLookupParams, teamType: AppleTeamType): Promise<boolean>;
@@ -10,6 +10,8 @@ exports.getAscApiKeysFromAccountAsync = getAscApiKeysFromAccountAsync;
10
10
  exports.selectAscApiKeysFromAccountAsync = selectAscApiKeysFromAccountAsync;
11
11
  exports.sortAscApiKeysByUpdatedAtDesc = sortAscApiKeysByUpdatedAtDesc;
12
12
  exports.formatAscApiKey = formatAscApiKey;
13
+ exports.resolveAscApiKeyForAppCredentialsAsync = resolveAscApiKeyForAppCredentialsAsync;
14
+ exports.tryAuthenticateAppStoreWithEasAscApiKeyAsync = tryAuthenticateAppStoreWithEasAscApiKeyAsync;
13
15
  const tslib_1 = require("tslib");
14
16
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
15
17
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
@@ -17,10 +19,14 @@ const nanoid_1 = require("nanoid");
17
19
  const path_1 = tslib_1.__importDefault(require("path"));
18
20
  const apple_utils_1 = require("@expo/apple-utils");
19
21
  const AppleTeamFormatting_1 = require("./AppleTeamFormatting");
22
+ const AppStoreConnectApiKeyQuery_1 = require("../../../graphql/queries/AppStoreConnectApiKeyQuery");
20
23
  const log_1 = tslib_1.__importStar(require("../../../log"));
21
24
  const prompts_1 = require("../../../prompts");
22
25
  const date_1 = require("../../../utils/date");
23
26
  const promptForCredentials_1 = require("../../utils/promptForCredentials");
27
+ const GraphqlClient_1 = require("../api/GraphqlClient");
28
+ const authenticateTypes_1 = require("../appstore/authenticateTypes");
29
+ const resolveCredentials_1 = require("../appstore/resolveCredentials");
24
30
  const credentials_1 = require("../credentials");
25
31
  const validateAscApiKey_1 = require("../validators/validateAscApiKey");
26
32
  var AppStoreApiKeyPurpose;
@@ -210,3 +216,61 @@ function formatAscApiKey(ascApiKey) {
210
216
  line += chalk_1.default.gray(`\n Updated: ${(0, date_1.fromNow)(new Date(updatedAt))} ago`);
211
217
  return line;
212
218
  }
219
+ async function resolveAscApiKeyForAppCredentialsAsync({ graphqlClient, app, }) {
220
+ const ascKeyFragment = await (0, GraphqlClient_1.getAscApiKeyForAppSubmissionsAsync)(graphqlClient, app);
221
+ if (!ascKeyFragment) {
222
+ return null;
223
+ }
224
+ const fullKey = await AppStoreConnectApiKeyQuery_1.AppStoreConnectApiKeyQuery.getByIdAsync(graphqlClient, ascKeyFragment.id);
225
+ return {
226
+ ascApiKey: {
227
+ keyP8: fullKey.keyP8,
228
+ keyId: fullKey.keyIdentifier,
229
+ issuerId: fullKey.issuerIdentifier,
230
+ },
231
+ teamId: ascKeyFragment.appleTeam?.appleTeamIdentifier,
232
+ teamName: ascKeyFragment.appleTeam?.appleTeamName ?? undefined,
233
+ };
234
+ }
235
+ /**
236
+ * Best-effort helper that populates `ctx.appStore.authCtx` in non-interactive mode
237
+ * by loading an App Store Connect API key (from env vars, or by fetching the app's
238
+ * stored key from the www GraphQL API).
239
+ *
240
+ * Returns true if `ctx.appStore.authCtx` is set after the call, false otherwise.
241
+ * Never throws.
242
+ */
243
+ async function tryAuthenticateAppStoreWithEasAscApiKeyAsync(ctx, app, teamType) {
244
+ if (ctx.appStore.authCtx) {
245
+ return true;
246
+ }
247
+ try {
248
+ if ((0, resolveCredentials_1.hasAscEnvVars)()) {
249
+ await ctx.appStore.ensureAuthenticatedAsync({
250
+ mode: authenticateTypes_1.AuthenticationMode.API_KEY,
251
+ teamType,
252
+ });
253
+ return !!ctx.appStore.authCtx;
254
+ }
255
+ const resolvedKey = await resolveAscApiKeyForAppCredentialsAsync({
256
+ graphqlClient: ctx.graphqlClient,
257
+ app,
258
+ });
259
+ if (!resolvedKey) {
260
+ return false;
261
+ }
262
+ log_1.default.log('Using App Store Connect API Key from EAS credentials service.');
263
+ await ctx.appStore.ensureAuthenticatedAsync({
264
+ mode: authenticateTypes_1.AuthenticationMode.API_KEY,
265
+ ascApiKey: resolvedKey.ascApiKey,
266
+ teamId: resolvedKey.teamId,
267
+ teamName: resolvedKey.teamName,
268
+ teamType,
269
+ });
270
+ return !!ctx.appStore.authCtx;
271
+ }
272
+ catch (err) {
273
+ log_1.default.warn(`Failed to authenticate with the App Store Connect API key from EAS credentials service: ${err.message ?? err}`);
274
+ return false;
275
+ }
276
+ }
@@ -8,7 +8,6 @@ const log_1 = tslib_1.__importStar(require("../../../log"));
8
8
  const ora_1 = require("../../../ora");
9
9
  const target_1 = require("../../../project/ios/target");
10
10
  const errors_1 = require("../../errors");
11
- const authenticateTypes_1 = require("../appstore/authenticateTypes");
12
11
  class ConfigureProvisioningProfile {
13
12
  app;
14
13
  target;
@@ -24,9 +23,8 @@ class ConfigureProvisioningProfile {
24
23
  if (ctx.freezeCredentials) {
25
24
  throw new errors_1.ForbidCredentialModificationError('Remove the --freeze-credentials flag to configure a Provisioning Profile.');
26
25
  }
27
- else if (ctx.nonInteractive &&
28
- ctx.appStore.defaultAuthenticationMode !== authenticateTypes_1.AuthenticationMode.API_KEY) {
29
- throw new errors_1.InsufficientAuthenticationNonInteractiveError(`In order to configure your Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. ${(0, log_1.learnMore)('https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team')}`);
26
+ else if (ctx.nonInteractive && !ctx.appStore.authCtx) {
27
+ throw new errors_1.InsufficientAuthenticationNonInteractiveError(`In order to configure your Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. Either set the EXPO_ASC_API_KEY_PATH/EXPO_ASC_KEY_ID/EXPO_ASC_ISSUER_ID environment variables, or configure an App Store Connect API Key for submissions for bundle identifier ${this.app.bundleIdentifier} on EAS. ${(0, log_1.learnMore)('https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team')}`);
30
28
  }
31
29
  const { developerPortalIdentifier, provisioningProfile } = this.originalProvisioningProfile;
32
30
  if (!developerPortalIdentifier && !provisioningProfile) {
@@ -9,7 +9,6 @@ const ProvisioningProfileUtils_1 = require("./ProvisioningProfileUtils");
9
9
  const log_1 = tslib_1.__importStar(require("../../../log"));
10
10
  const errors_1 = require("../../errors");
11
11
  const promptForCredentials_1 = require("../../utils/promptForCredentials");
12
- const authenticateTypes_1 = require("../appstore/authenticateTypes");
13
12
  const credentials_1 = require("../credentials");
14
13
  class CreateProvisioningProfile {
15
14
  app;
@@ -24,9 +23,8 @@ class CreateProvisioningProfile {
24
23
  if (ctx.freezeCredentials) {
25
24
  throw new errors_1.ForbidCredentialModificationError('Run this command again without the --freeze-credentials flag in order to generate a new Provisioning Profile.');
26
25
  }
27
- else if (ctx.nonInteractive &&
28
- ctx.appStore.defaultAuthenticationMode !== authenticateTypes_1.AuthenticationMode.API_KEY) {
29
- throw new errors_1.InsufficientAuthenticationNonInteractiveError(`In order to generate a new Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. ${(0, log_1.learnMore)('https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team')}`);
26
+ else if (ctx.nonInteractive && !ctx.appStore.authCtx) {
27
+ throw new errors_1.InsufficientAuthenticationNonInteractiveError(`In order to generate a new Provisioning Profile, authentication with an ASC API key is required in non-interactive mode. Either set the EXPO_ASC_API_KEY_PATH/EXPO_ASC_KEY_ID/EXPO_ASC_ISSUER_ID environment variables, or configure an App Store Connect API Key for submissions for bundle identifier ${this.app.bundleIdentifier} on EAS. ${(0, log_1.learnMore)('https://docs.expo.dev/build/building-on-ci/#optional-provide-an-asc-api-token-for-your-apple-team')}`);
30
28
  }
31
29
  const appleAuthCtx = await ctx.appStore.ensureAuthenticatedAsync();
32
30
  const provisioningProfile = await this.provideOrGenerateAsync(ctx, appleAuthCtx);