eas-cli 18.4.0 → 18.5.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 (53) hide show
  1. package/README.md +92 -90
  2. package/build/build/android/prepareJob.js +2 -2
  3. package/build/build/ios/prepareJob.js +2 -2
  4. package/build/build/metadata.js +2 -1
  5. package/build/commandUtils/EasCommand.js +23 -2
  6. package/build/commandUtils/context/contextUtils/getProjectIdAsync.js +2 -0
  7. package/build/commandUtils/workflow/fetchLogs.js +11 -2
  8. package/build/commandUtils/workflow/types.d.ts +5 -1
  9. package/build/commandUtils/workflow/utils.js +22 -16
  10. package/build/commands/metadata/pull.d.ts +1 -0
  11. package/build/commands/metadata/pull.js +11 -4
  12. package/build/commands/metadata/push.d.ts +1 -0
  13. package/build/commands/metadata/push.js +11 -4
  14. package/build/commands/project/onboarding.js +3 -0
  15. package/build/commands/workflow/logs.js +12 -12
  16. package/build/graphql/generated.d.ts +673 -46
  17. package/build/graphql/generated.js +58 -20
  18. package/build/graphql/queries/UserQuery.js +3 -0
  19. package/build/graphql/types/Update.js +3 -0
  20. package/build/metadata/apple/config/reader.d.ts +5 -1
  21. package/build/metadata/apple/config/reader.js +8 -0
  22. package/build/metadata/apple/config/writer.d.ts +5 -1
  23. package/build/metadata/apple/config/writer.js +13 -0
  24. package/build/metadata/apple/data.d.ts +6 -2
  25. package/build/metadata/apple/rules/infoRestrictedWords.js +6 -1
  26. package/build/metadata/apple/tasks/age-rating.d.ts +1 -1
  27. package/build/metadata/apple/tasks/age-rating.js +19 -3
  28. package/build/metadata/apple/tasks/app-review-detail.js +7 -2
  29. package/build/metadata/apple/tasks/index.js +4 -0
  30. package/build/metadata/apple/tasks/previews.d.ts +18 -0
  31. package/build/metadata/apple/tasks/previews.js +208 -0
  32. package/build/metadata/apple/tasks/screenshots.d.ts +18 -0
  33. package/build/metadata/apple/tasks/screenshots.js +224 -0
  34. package/build/metadata/apple/types.d.ts +34 -1
  35. package/build/metadata/auth.d.ts +11 -1
  36. package/build/metadata/auth.js +96 -2
  37. package/build/metadata/download.d.ts +5 -1
  38. package/build/metadata/download.js +16 -8
  39. package/build/metadata/upload.d.ts +5 -1
  40. package/build/metadata/upload.js +11 -4
  41. package/build/project/projectUtils.d.ts +0 -2
  42. package/build/project/projectUtils.js +0 -12
  43. package/build/project/workflow.js +1 -1
  44. package/build/sentry.d.ts +2 -0
  45. package/build/sentry.js +22 -0
  46. package/build/update/utils.d.ts +2 -2
  47. package/build/update/utils.js +1 -0
  48. package/build/user/User.d.ts +2 -2
  49. package/build/user/User.js +3 -0
  50. package/build/user/expoBrowserAuthFlowLauncher.js +70 -13
  51. package/oclif.manifest.json +13 -1
  52. package/package.json +12 -11
  53. package/schema/metadata-0.json +36 -0
@@ -6,13 +6,13 @@ const eas_build_job_1 = require("@expo/eas-build-job");
6
6
  const path_1 = tslib_1.__importDefault(require("path"));
7
7
  const slash_1 = tslib_1.__importDefault(require("slash"));
8
8
  const customBuildConfig_1 = require("../../project/customBuildConfig");
9
- const projectUtils_1 = require("../../project/projectUtils");
9
+ const User_1 = require("../../user/User");
10
10
  const cacheDefaults = {
11
11
  disabled: false,
12
12
  paths: [],
13
13
  };
14
14
  async function prepareJobAsync(ctx, jobData) {
15
- const username = (0, projectUtils_1.getUsernameForBuildMetadataAndBuildJob)(ctx.user);
15
+ const username = (0, User_1.getActorUsername)(ctx.user) ?? undefined;
16
16
  const buildProfile = ctx.buildProfile;
17
17
  const projectRootDirectory = (0, slash_1.default)(path_1.default.relative(await ctx.vcsClient.getRootPathAsync(), ctx.projectDir)) || '.';
18
18
  const { credentials } = jobData;
@@ -8,13 +8,13 @@ const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
8
8
  const path_1 = tslib_1.__importDefault(require("path"));
9
9
  const slash_1 = tslib_1.__importDefault(require("slash"));
10
10
  const customBuildConfig_1 = require("../../project/customBuildConfig");
11
- const projectUtils_1 = require("../../project/projectUtils");
11
+ const User_1 = require("../../user/User");
12
12
  const cacheDefaults = {
13
13
  disabled: false,
14
14
  paths: [],
15
15
  };
16
16
  async function prepareJobAsync(ctx, jobData) {
17
- const username = (0, projectUtils_1.getUsernameForBuildMetadataAndBuildJob)(ctx.user);
17
+ const username = (0, User_1.getActorUsername)(ctx.user) ?? undefined;
18
18
  const buildProfile = ctx.buildProfile;
19
19
  const projectRootDirectory = (0, slash_1.default)(path_1.default.relative(await ctx.vcsClient.getRootPathAsync(), ctx.projectDir)) || '.';
20
20
  const buildCredentials = {};
@@ -16,6 +16,7 @@ const projectUtils_1 = require("../project/projectUtils");
16
16
  const UpdatesModule_1 = require("../update/android/UpdatesModule");
17
17
  const UpdatesModule_2 = require("../update/ios/UpdatesModule");
18
18
  const easCli_1 = require("../utils/easCli");
19
+ const User_1 = require("../user/User");
19
20
  async function collectMetadataAsync(ctx, runtimeAndFingerprintMetadata) {
20
21
  const channelObject = await resolveChannelAsync(ctx);
21
22
  const distribution = ctx.buildProfile.distribution ?? types_1.BuildDistributionType.STORE;
@@ -39,7 +40,7 @@ async function collectMetadataAsync(ctx, runtimeAndFingerprintMetadata) {
39
40
  isGitWorkingTreeDirty: ctx.localBuildOptions.localBuildMode === local_1.LocalBuildMode.INTERNAL
40
41
  ? false
41
42
  : await ctx.vcsClient.hasUncommittedChangesAsync(),
42
- username: (0, projectUtils_1.getUsernameForBuildMetadataAndBuildJob)(ctx.user),
43
+ username: (0, User_1.getActorUsername)(ctx.user) ?? undefined,
43
44
  message: ctx.message,
44
45
  ...(ctx.platform === eas_build_job_1.Platform.IOS && {
45
46
  iosEnterpriseProvisioning: resolveIosEnterpriseProvisioning(ctx),
@@ -20,7 +20,9 @@ const VcsClientContextField_1 = tslib_1.__importDefault(require("./context/VcsCl
20
20
  const errors_1 = require("./errors");
21
21
  const AnalyticsManager_1 = require("../analytics/AnalyticsManager");
22
22
  const log_1 = tslib_1.__importStar(require("../log"));
23
+ const sentry_1 = tslib_1.__importDefault(require("../sentry"));
23
24
  const SessionManager_1 = tslib_1.__importDefault(require("../user/SessionManager"));
25
+ const User_1 = require("../user/User");
24
26
  const BASE_GRAPHQL_ERROR_MESSAGE = 'GraphQL request failed.';
25
27
  class EasCommand extends core_1.Command {
26
28
  static ContextOptions = {
@@ -159,7 +161,13 @@ class EasCommand extends core_1.Command {
159
161
  this.sessionManagerInternal = new SessionManager_1.default(this.analytics);
160
162
  // this is needed for logEvent call below as it identifies the user in the analytics system
161
163
  // if possible
162
- await this.sessionManager.getUserAsync();
164
+ const actor = await this.sessionManager.getUserAsync();
165
+ if (actor) {
166
+ sentry_1.default.setUser({
167
+ id: actor.id,
168
+ username: (0, User_1.getActorDisplayName)(actor),
169
+ });
170
+ }
163
171
  this.analytics.logEvent(AnalyticsManager_1.CommandEvent.ACTION, {
164
172
  // id is assigned by oclif in constructor based on the filepath:
165
173
  // commands/submit === submit, commands/build/list === build:list
@@ -170,10 +178,12 @@ class EasCommand extends core_1.Command {
170
178
  // eslint-disable-next-line async-protect/async-suffix
171
179
  async finally(err) {
172
180
  await this.analytics.flushAsync();
181
+ await sentry_1.default.flush(2000);
173
182
  return await super.finally(err);
174
183
  }
175
184
  catch(err) {
176
- let baseMessage = `${this.id} command failed.`;
185
+ const commandId = this.id ?? 'unknown';
186
+ let baseMessage = `${commandId} command failed.`;
177
187
  if (err instanceof errors_1.EasCommandError) {
178
188
  log_1.default.error(err.message);
179
189
  }
@@ -206,6 +216,17 @@ class EasCommand extends core_1.Command {
206
216
  log_1.default.error(err.message);
207
217
  }
208
218
  log_1.default.debug(err);
219
+ sentry_1.default.withScope(scope => {
220
+ scope.setTag('command', commandId);
221
+ scope.setTag('error_name', err.name);
222
+ scope.setExtra('commandId', commandId);
223
+ scope.setExtra('commandMessage', baseMessage);
224
+ scope.setExtra('originalMessage', err.message);
225
+ if (err instanceof core_2.CombinedError) {
226
+ scope.setExtra('graphQLErrorCount', err.graphQLErrors.length);
227
+ }
228
+ sentry_1.default.captureException(err);
229
+ });
209
230
  const sanitizedError = new Error(baseMessage);
210
231
  sanitizedError.stack = err.stack;
211
232
  throw sanitizedError;
@@ -114,6 +114,8 @@ async function validateOrSetProjectIdAsync({ exp, graphqlClient, actor, options,
114
114
  return user.username;
115
115
  case 'SSOUser':
116
116
  return user.username;
117
+ case 'PartnerActor':
118
+ return user.username;
117
119
  case 'Robot':
118
120
  throw new Error('Must configure EAS project by running "eas init" before using a robot user to manage the project.');
119
121
  }
@@ -16,6 +16,16 @@ async function fetchRawLogsForCustomJobAsync(job) {
16
16
  return rawLogs;
17
17
  }
18
18
  async function fetchRawLogsForBuildJobAsync(state, job) {
19
+ // Prefer turtleJobRun logs, which contain JSONL-formatted step logs
20
+ // for both built-in and custom build steps
21
+ const turtleLogFileUrl = job.turtleJobRun?.logFileUrls?.[0];
22
+ if (turtleLogFileUrl) {
23
+ const response = await fetch(turtleLogFileUrl, {
24
+ method: 'GET',
25
+ });
26
+ return await response.text();
27
+ }
28
+ // Fall back to build logFiles
19
29
  const buildId = job.outputs?.build_id;
20
30
  if (!buildId) {
21
31
  return null;
@@ -30,6 +40,5 @@ async function fetchRawLogsForBuildJobAsync(state, job) {
30
40
  const response = await fetch(firstLogFileUrl, {
31
41
  method: 'GET',
32
42
  });
33
- const rawLogs = await response.text();
34
- return rawLogs;
43
+ return await response.text();
35
44
  }
@@ -37,4 +37,8 @@ export type WorkflowLogLine = {
37
37
  marker?: string;
38
38
  err?: any;
39
39
  };
40
- export type WorkflowLogs = Map<string, WorkflowLogLine[]>;
40
+ export type WorkflowLogs = Map<string, {
41
+ key: string;
42
+ label: string;
43
+ logLines: WorkflowLogLine[];
44
+ }>;
@@ -74,15 +74,14 @@ function choiceFromWorkflowJob(job, index) {
74
74
  };
75
75
  }
76
76
  function choicesFromWorkflowLogs(logs) {
77
- return Array.from(logs.keys())
78
- .map(step => {
79
- const logLines = logs.get(step);
80
- const stepStatus = logLines?.filter((line) => line.marker === 'end-step')[0]?.result ?? '';
77
+ return Array.from(logs.values())
78
+ .map(({ key, label, logLines }) => {
79
+ const stepStatus = logLines?.filter((line) => line.marker === 'end-step' || line.marker === 'END_PHASE')[0]?.result ?? '';
81
80
  return {
82
- title: `${step} - ${stepStatus}`,
83
- name: step,
81
+ title: `${label} - ${stepStatus}`,
82
+ name: label,
84
83
  status: stepStatus,
85
- value: step,
84
+ value: key,
86
85
  logLines,
87
86
  };
88
87
  })
@@ -123,19 +122,26 @@ async function fetchAndProcessLogsFromJobAsync(state, job) {
123
122
  }
124
123
  log_1.default.debug(`rawLogs = ${JSON.stringify(rawLogs, null, 2)}`);
125
124
  const logs = new Map();
126
- const logKeys = new Set();
127
125
  rawLogs.split('\n').forEach((line, index) => {
128
126
  log_1.default.debug(`line ${index} = ${JSON.stringify(line, null, 2)}`);
129
127
  try {
130
128
  const parsedLine = JSON.parse(line);
131
- const { buildStepDisplayName, buildStepInternalId, time, msg, result, marker, err } = parsedLine;
132
- const stepId = buildStepDisplayName ?? buildStepInternalId;
133
- if (stepId) {
134
- if (!logKeys.has(stepId)) {
135
- logKeys.add(stepId);
136
- logs.set(stepId, []);
129
+ const { buildStepDisplayName, buildStepId, phase, time, msg, result, marker, err } = parsedLine;
130
+ const stepKey = buildStepId ?? buildStepDisplayName ?? phase;
131
+ const stepLabel = buildStepDisplayName ?? buildStepId ?? phase;
132
+ if (stepKey && stepLabel) {
133
+ if (!logs.has(stepKey)) {
134
+ logs.set(stepKey, {
135
+ key: stepKey,
136
+ label: stepLabel,
137
+ logLines: [],
138
+ });
137
139
  }
138
- logs.get(stepId)?.push({ time, msg, result, marker, err });
140
+ const logGroup = logs.get(stepKey);
141
+ if (buildStepDisplayName) {
142
+ logGroup.label = buildStepDisplayName;
143
+ }
144
+ logGroup.logLines.push({ time, msg, result, marker, err });
139
145
  }
140
146
  }
141
147
  catch { }
@@ -212,7 +218,7 @@ async function infoForFailedWorkflowRunAsync(graphqlClient, workflowRun, maxLogL
212
218
  statusValues.push({ label: '', value: '' });
213
219
  statusValues.push({ label: ' Failed job', value: job.name });
214
220
  if (steps.length > 0) {
215
- const failedStep = steps.find(step => step.status === 'fail');
221
+ const failedStep = steps.find(step => step.status === 'fail' || step.status === 'failed');
216
222
  if (failedStep) {
217
223
  const logs = failedStep.logLines?.map(line => line.msg).slice(-logLinesToKeep) ?? [];
218
224
  statusValues.push({ label: ' Failed step', value: failedStep.name });
@@ -3,6 +3,7 @@ export default class MetadataPull extends EasCommand {
3
3
  static description: string;
4
4
  static flags: {
5
5
  profile: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
6
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
6
7
  };
7
8
  static contextDefinition: {
8
9
  vcsClient: import("../../commandUtils/context/VcsClientContextField").default;
@@ -20,6 +20,10 @@ class MetadataPull extends EasCommand_1.default {
20
20
  char: 'e',
21
21
  description: 'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
22
22
  }),
23
+ 'non-interactive': core_1.Flags.boolean({
24
+ default: false,
25
+ description: 'Run the command in non-interactive mode.',
26
+ }),
23
27
  };
24
28
  static contextDefinition = {
25
29
  ...this.ContextOptions.ProjectConfig,
@@ -30,12 +34,12 @@ class MetadataPull extends EasCommand_1.default {
30
34
  async runAsync() {
31
35
  log_1.default.warn('EAS Metadata is in beta and subject to breaking changes.');
32
36
  const { flags } = await this.parse(MetadataPull);
37
+ const nonInteractive = flags['non-interactive'];
33
38
  const { loggedIn: { actor, graphqlClient }, privateProjectConfig: { exp, projectId, projectDir }, analytics, vcsClient, } = await this.getContextAsync(MetadataPull, {
34
- nonInteractive: false,
39
+ nonInteractive,
35
40
  withServerSideEnvironment: null,
36
41
  });
37
- // this command is interactive (all nonInteractive flags passed to utility functions are false)
38
- await (0, configure_1.ensureProjectConfiguredAsync)({ projectDir, nonInteractive: false, vcsClient });
42
+ await (0, configure_1.ensureProjectConfiguredAsync)({ projectDir, nonInteractive, vcsClient });
39
43
  const submitProfiles = await (0, profiles_1.getProfilesAsync)({
40
44
  type: 'submit',
41
45
  easJsonAccessor: eas_json_1.EasJsonAccessor.fromProjectPath(projectDir),
@@ -53,7 +57,7 @@ class MetadataPull extends EasCommand_1.default {
53
57
  user: actor,
54
58
  graphqlClient,
55
59
  analytics,
56
- nonInteractive: false,
60
+ nonInteractive,
57
61
  vcsClient,
58
62
  });
59
63
  try {
@@ -63,6 +67,9 @@ class MetadataPull extends EasCommand_1.default {
63
67
  credentialsCtx,
64
68
  projectDir,
65
69
  profile: submitProfile,
70
+ nonInteractive,
71
+ graphqlClient,
72
+ projectId,
66
73
  });
67
74
  const relativePath = path_1.default.relative(process.cwd(), filePath);
68
75
  log_1.default.addNewLineIfNone();
@@ -3,6 +3,7 @@ export default class MetadataPush extends EasCommand {
3
3
  static description: string;
4
4
  static flags: {
5
5
  profile: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
6
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
6
7
  };
7
8
  static contextDefinition: {
8
9
  vcsClient: import("../../commandUtils/context/VcsClientContextField").default;
@@ -18,6 +18,10 @@ class MetadataPush extends EasCommand_1.default {
18
18
  char: 'e',
19
19
  description: 'Name of the submit profile from eas.json. Defaults to "production" if defined in eas.json.',
20
20
  }),
21
+ 'non-interactive': core_1.Flags.boolean({
22
+ default: false,
23
+ description: 'Run the command in non-interactive mode.',
24
+ }),
21
25
  };
22
26
  static contextDefinition = {
23
27
  ...this.ContextOptions.ProjectConfig,
@@ -28,12 +32,12 @@ class MetadataPush extends EasCommand_1.default {
28
32
  async runAsync() {
29
33
  log_1.default.warn('EAS Metadata is in beta and subject to breaking changes.');
30
34
  const { flags } = await this.parse(MetadataPush);
35
+ const nonInteractive = flags['non-interactive'];
31
36
  const { loggedIn: { actor, graphqlClient }, privateProjectConfig: { exp, projectId, projectDir }, analytics, vcsClient, } = await this.getContextAsync(MetadataPush, {
32
- nonInteractive: false,
37
+ nonInteractive,
33
38
  withServerSideEnvironment: null,
34
39
  });
35
- // this command is interactive (all nonInteractive flags passed to utility functions are false)
36
- await (0, configure_1.ensureProjectConfiguredAsync)({ projectDir, nonInteractive: false, vcsClient });
40
+ await (0, configure_1.ensureProjectConfiguredAsync)({ projectDir, nonInteractive, vcsClient });
37
41
  const submitProfiles = await (0, profiles_1.getProfilesAsync)({
38
42
  type: 'submit',
39
43
  easJsonAccessor: eas_json_1.EasJsonAccessor.fromProjectPath(projectDir),
@@ -51,7 +55,7 @@ class MetadataPush extends EasCommand_1.default {
51
55
  user: actor,
52
56
  graphqlClient,
53
57
  analytics,
54
- nonInteractive: false,
58
+ nonInteractive,
55
59
  vcsClient,
56
60
  });
57
61
  try {
@@ -61,6 +65,9 @@ class MetadataPush extends EasCommand_1.default {
61
65
  credentialsCtx,
62
66
  projectDir,
63
67
  profile: submitProfile,
68
+ nonInteractive,
69
+ graphqlClient,
70
+ projectId,
64
71
  });
65
72
  log_1.default.addNewLineIfNone();
66
73
  log_1.default.log(`🎉 Store configuration is synced with the app stores.
@@ -44,6 +44,9 @@ class Onboarding extends EasCommand_1.default {
44
44
  if (actor.__typename === 'Robot') {
45
45
  throw new Error('This command is not available for robot users. Make sure you are not using a robot token and try again.');
46
46
  }
47
+ if (actor.__typename === 'PartnerActor') {
48
+ throw new Error('This command is not available for partner actors. Make sure you are using a user token and try again.');
49
+ }
47
50
  if (!actor.preferences.onboarding) {
48
51
  throw new Error('This command can only be run as part of the onboarding process started on the Expo website. Visit https://expo.new to start a new project.');
49
52
  }
@@ -8,14 +8,14 @@ const stateMachine_1 = require("../../commandUtils/workflow/stateMachine");
8
8
  const log_1 = tslib_1.__importDefault(require("../../log"));
9
9
  const json_1 = require("../../utils/json");
10
10
  function printLogsForAllSteps(logs) {
11
- [...logs.keys()].forEach(step => {
12
- const logLines = logs.get(step);
13
- if (logLines) {
14
- log_1.default.log(`Step: ${step}`);
15
- logLines.forEach(line => {
16
- log_1.default.log(` ${line.time} ${line.msg}`);
17
- });
11
+ [...logs.values()].forEach(({ label, logLines }) => {
12
+ if (logLines.length === 0) {
13
+ return;
18
14
  }
15
+ log_1.default.log(`Step: ${label}`);
16
+ logLines.forEach(line => {
17
+ log_1.default.log(` ${line.time} ${line.msg}`);
18
+ });
19
19
  log_1.default.addNewLineIfNone();
20
20
  });
21
21
  }
@@ -68,7 +68,7 @@ class WorkflowLogView extends EasCommand_1.default {
68
68
  if (allSteps) {
69
69
  if (logs) {
70
70
  if (flags.json) {
71
- (0, json_1.printJsonOnlyOutput)(Object.fromEntries(logs));
71
+ (0, json_1.printJsonOnlyOutput)(Object.fromEntries(Array.from(logs.entries()).map(([key, value]) => [key, value.logLines])));
72
72
  }
73
73
  else {
74
74
  printLogsForAllSteps(logs);
@@ -77,15 +77,15 @@ class WorkflowLogView extends EasCommand_1.default {
77
77
  }
78
78
  else {
79
79
  const selectedStep = finalSelectionState?.step;
80
- const logLines = logs?.get(selectedStep);
81
- if (logLines) {
80
+ const logGroup = logs?.get(selectedStep);
81
+ if (logGroup) {
82
82
  if (flags.json) {
83
83
  const output = {};
84
- output[selectedStep] = logLines ?? null;
84
+ output[selectedStep] = logGroup.logLines;
85
85
  (0, json_1.printJsonOnlyOutput)(output);
86
86
  }
87
87
  else {
88
- logLines.forEach(line => {
88
+ logGroup.logLines.forEach(line => {
89
89
  log_1.default.log(` ${line.time} ${line.msg}`);
90
90
  });
91
91
  }