eas-cli 18.10.0 → 18.12.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 (56) hide show
  1. package/README.md +107 -102
  2. package/build/build/runBuildAndSubmit.d.ts +3 -1
  3. package/build/build/runBuildAndSubmit.js +12 -4
  4. package/build/build/utils/repository.js +8 -0
  5. package/build/build/validateLockfile.d.ts +1 -0
  6. package/build/build/validateLockfile.js +37 -0
  7. package/build/commandUtils/buildFlags.d.ts +1 -0
  8. package/build/commandUtils/buildFlags.js +18 -0
  9. package/build/commandUtils/convex.d.ts +1 -0
  10. package/build/commandUtils/convex.js +8 -2
  11. package/build/commands/build/dev.d.ts +1 -0
  12. package/build/commands/build/dev.js +12 -2
  13. package/build/commands/build/run.d.ts +1 -0
  14. package/build/commands/build/run.js +12 -3
  15. package/build/commands/integrations/convex/connect.d.ts +1 -0
  16. package/build/commands/integrations/convex/connect.js +65 -7
  17. package/build/commands/integrations/convex/team/invite.js +6 -1
  18. package/build/commands/observe/events.js +12 -24
  19. package/build/commands/observe/logs.d.ts +29 -0
  20. package/build/commands/observe/logs.js +163 -0
  21. package/build/commands/observe/metrics.js +11 -19
  22. package/build/commands/observe/versions.js +11 -19
  23. package/build/commands/simulator/start.js +85 -92
  24. package/build/commands/simulator/stop.js +1 -1
  25. package/build/graphql/generated.d.ts +290 -10
  26. package/build/graphql/generated.js +32 -3
  27. package/build/graphql/mutations/DeviceRunSessionMutation.d.ts +2 -2
  28. package/build/graphql/mutations/DeviceRunSessionMutation.js +4 -4
  29. package/build/graphql/queries/DeviceRunSessionQuery.js +8 -1
  30. package/build/graphql/queries/ObserveQuery.d.ts +21 -1
  31. package/build/graphql/queries/ObserveQuery.js +80 -0
  32. package/build/graphql/types/ConvexTeamConnection.d.ts +1 -1
  33. package/build/graphql/types/ConvexTeamConnection.js +1 -0
  34. package/build/graphql/types/Observe.d.ts +1 -0
  35. package/build/graphql/types/Observe.js +26 -1
  36. package/build/observe/fetchCustomEvents.d.ts +19 -0
  37. package/build/observe/fetchCustomEvents.js +21 -0
  38. package/build/observe/formatCustomEvents.d.ts +70 -0
  39. package/build/observe/formatCustomEvents.js +140 -0
  40. package/build/observe/formatEvents.js +5 -34
  41. package/build/observe/formatMetrics.js +2 -7
  42. package/build/observe/formatUtils.d.ts +27 -0
  43. package/build/observe/formatUtils.js +64 -0
  44. package/build/observe/formatVersions.js +2 -9
  45. package/build/observe/platforms.d.ts +21 -0
  46. package/build/observe/platforms.js +48 -0
  47. package/build/observe/resolveProjectContext.d.ts +22 -0
  48. package/build/observe/resolveProjectContext.js +21 -0
  49. package/build/run/ios/run.d.ts +2 -1
  50. package/build/run/ios/run.js +6 -2
  51. package/build/run/ios/simulator.d.ts +4 -1
  52. package/build/run/ios/simulator.js +14 -2
  53. package/build/run/run.d.ts +2 -1
  54. package/build/run/run.js +2 -2
  55. package/oclif.manifest.json +568 -375
  56. package/package.json +5 -5
@@ -7,6 +7,7 @@ import { DynamicConfigContextFn } from '../commandUtils/context/DynamicProjectCo
7
7
  import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
8
8
  import { BuildFragment } from '../graphql/generated';
9
9
  import { RequestedPlatform } from '../platform';
10
+ import { SimulatorRunTarget } from '../run/ios/run';
10
11
  import { Actor } from '../user/User';
11
12
  import { ProfileData } from '../utils/profiles';
12
13
  import { Client } from '../vcs/vcs';
@@ -26,6 +27,7 @@ export interface BuildFlags {
26
27
  freezeCredentials: boolean;
27
28
  isVerboseLoggingEnabled?: boolean;
28
29
  whatToTest?: string;
30
+ simulator?: SimulatorRunTarget;
29
31
  }
30
32
  export declare function runBuildAndSubmitAsync({ graphqlClient, analytics, vcsClient, projectDir, flags, actor, getDynamicPrivateProjectConfigAsync, downloadSimBuildAutoConfirm, envOverride, }: {
31
33
  graphqlClient: ExpoGraphqlClient;
@@ -41,4 +43,4 @@ export declare function runBuildAndSubmitAsync({ graphqlClient, analytics, vcsCl
41
43
  buildIds: string[];
42
44
  buildProfiles?: ProfileData<'build'>[];
43
45
  }>;
44
- export declare function downloadAndRunAsync(build: BuildFragment): Promise<void>;
46
+ export declare function downloadAndRunAsync(build: BuildFragment, simulator?: SimulatorRunTarget): Promise<void>;
@@ -17,6 +17,7 @@ const createContext_1 = require("./createContext");
17
17
  const evaluateConfigWithEnvVarsAsync_1 = require("./evaluateConfigWithEnvVarsAsync");
18
18
  const build_3 = require("./ios/build");
19
19
  const local_1 = require("./local");
20
+ const validateLockfile_1 = require("./validateLockfile");
20
21
  const devClient_1 = require("./utils/devClient");
21
22
  const printBuildInfo_1 = require("./utils/printBuildInfo");
22
23
  const repository_1 = require("./utils/repository");
@@ -46,6 +47,7 @@ const profiles_1 = require("../utils/profiles");
46
47
  const checkForOverages_1 = require("../utils/usage/checkForOverages");
47
48
  let metroConfigValidated = false;
48
49
  let sdkVersionChecked = false;
50
+ let lockfileChecked = false;
49
51
  let hasWarnedAboutUsageOverages = false;
50
52
  async function runBuildAndSubmitAsync({ graphqlClient, analytics, vcsClient, projectDir, flags, actor, getDynamicPrivateProjectConfigAsync, downloadSimBuildAutoConfirm, envOverride, }) {
51
53
  await vcsClient.ensureRepoExistsAsync();
@@ -245,6 +247,12 @@ async function prepareAndStartBuildAsync({ projectDir, flags, moreBuilds, buildP
245
247
  whatToTest: flags.whatToTest,
246
248
  env,
247
249
  });
250
+ if (!lockfileChecked && !flags.localBuildOptions.localBuildMode) {
251
+ if (!process.env.EAS_BUILD_SKIP_LOCKFILE_CHECK) {
252
+ await (0, validateLockfile_1.ensureLockfileExistsAsync)(projectDir);
253
+ }
254
+ lockfileChecked = true;
255
+ }
248
256
  if (!hasWarnedAboutUsageOverages && !flags.localBuildOptions.localBuildMode) {
249
257
  hasWarnedAboutUsageOverages = true;
250
258
  log_1.default.newLine();
@@ -358,17 +366,17 @@ function exitWithNonZeroCodeIfSomeBuildsFailed(maybeBuilds) {
358
366
  process.exit(1);
359
367
  }
360
368
  }
361
- async function downloadAndRunAsync(build) {
369
+ async function downloadAndRunAsync(build, simulator) {
362
370
  (0, assert_1.default)(build.artifacts?.applicationArchiveUrl);
363
371
  const cachedAppPath = (0, run_1.getEasBuildRunCachedAppPath)(build.project.id, build.id, build.platform);
364
372
  if (await (0, fs_extra_1.pathExists)(cachedAppPath)) {
365
373
  log_1.default.newLine();
366
374
  log_1.default.log(`Using cached app...`);
367
- await (0, run_1.runAsync)(cachedAppPath, build.platform);
375
+ await (0, run_1.runAsync)(cachedAppPath, build.platform, simulator);
368
376
  return;
369
377
  }
370
378
  const buildPath = await (0, download_1.downloadAndMaybeExtractAppAsync)(build.artifacts.applicationArchiveUrl, build.platform, cachedAppPath);
371
- await (0, run_1.runAsync)(buildPath, build.platform);
379
+ await (0, run_1.runAsync)(buildPath, build.platform, simulator);
372
380
  }
373
381
  async function maybeDownloadAndRunSimulatorBuildsAsync(builds, flags, autoConfirm) {
374
382
  const simBuilds = builds.filter(filter_1.truthy).filter(utils_1.isRunnableOnSimulatorOrEmulator);
@@ -381,7 +389,7 @@ async function maybeDownloadAndRunSimulatorBuildsAsync(builds, flags, autoConfir
381
389
  message: `Install and run the ${simBuild.platform === generated_1.AppPlatform.Android ? 'Android' : 'iOS'} build on ${simBuild.platform === generated_1.AppPlatform.Android ? 'an emulator' : 'a simulator'}?`,
382
390
  }));
383
391
  if (confirm) {
384
- await downloadAndRunAsync(simBuild);
392
+ await downloadAndRunAsync(simBuild, flags.simulator);
385
393
  }
386
394
  }
387
395
  }
@@ -114,6 +114,14 @@ async function makeProjectTarballAsync(vcsClient) {
114
114
  prefix: 'project',
115
115
  gzip: true,
116
116
  portable: true,
117
+ onWriteEntry(entry) {
118
+ // Read-only directories may have files inside them. This causes trouble on tar extraction.
119
+ // Hence, we're forcing the owner write bit on directories in Windows
120
+ // to avoid this issue.
121
+ if (entry.type === 'Directory' && entry.stat) {
122
+ entry.stat.mode |= 0o200;
123
+ }
124
+ },
117
125
  }, ['.']);
118
126
  }
119
127
  catch (err) {
@@ -0,0 +1 @@
1
+ export declare function ensureLockfileExistsAsync(projectDir: string): Promise<void>;
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureLockfileExistsAsync = ensureLockfileExistsAsync;
4
+ const tslib_1 = require("tslib");
5
+ const package_manager_1 = require("@expo/package-manager");
6
+ const fs_extra_1 = require("fs-extra");
7
+ const path_1 = tslib_1.__importDefault(require("path"));
8
+ const LOCKFILE_NAMES = [
9
+ package_manager_1.NPM_LOCK_FILE,
10
+ package_manager_1.YARN_LOCK_FILE,
11
+ package_manager_1.PNPM_LOCK_FILE,
12
+ package_manager_1.BUN_LOCK_FILE,
13
+ package_manager_1.BUN_TEXT_LOCK_FILE,
14
+ ];
15
+ async function hasLockfileAsync(dir) {
16
+ for (const lockfile of LOCKFILE_NAMES) {
17
+ if (await (0, fs_extra_1.pathExists)(path_1.default.join(dir, lockfile))) {
18
+ return true;
19
+ }
20
+ }
21
+ return false;
22
+ }
23
+ async function ensureLockfileExistsAsync(projectDir) {
24
+ if (await hasLockfileAsync(projectDir)) {
25
+ return;
26
+ }
27
+ const workspaceRoot = (0, package_manager_1.resolveWorkspaceRoot)(projectDir);
28
+ if (workspaceRoot && workspaceRoot !== projectDir) {
29
+ if (await hasLockfileAsync(workspaceRoot)) {
30
+ return;
31
+ }
32
+ }
33
+ throw new Error(`No lockfile found in the project directory.\n` +
34
+ `A lockfile is required to ensure deterministic dependency installation in EAS.\n` +
35
+ `Run your package manager's install command (e.g. "npm install") to generate one.\n` +
36
+ `To skip this check, run this command with EAS_BUILD_SKIP_LOCKFILE_CHECK=1.`);
37
+ }
@@ -0,0 +1 @@
1
+ export declare function preprocessBuildCommandArgs(argv: string[]): string[];
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.preprocessBuildCommandArgs = preprocessBuildCommandArgs;
4
+ function preprocessBuildCommandArgs(argv) {
5
+ return withOptionalSimulatorValue(argv);
6
+ }
7
+ // oclif does not support string flags with optional values. We want "--simulator"
8
+ // to prompt for a simulator, while still supporting "--simulator <name-or-udid>".
9
+ // Insert an empty string value for the bare flag before oclif parses argv.
10
+ function withOptionalSimulatorValue(argv) {
11
+ const simulatorFlagIndex = argv.indexOf('--simulator');
12
+ const simulatorValue = argv[simulatorFlagIndex + 1];
13
+ if (simulatorFlagIndex === -1 ||
14
+ (simulatorValue !== undefined && !simulatorValue.startsWith('-'))) {
15
+ return argv;
16
+ }
17
+ return [...argv.slice(0, simulatorFlagIndex + 1), '', ...argv.slice(simulatorFlagIndex + 1)];
18
+ }
@@ -1,4 +1,5 @@
1
1
  import { ConvexProjectData, ConvexTeamConnectionData } from '../graphql/types/ConvexTeamConnection';
2
+ export declare function formatConvexInviteTimestamp(timestamp: string): string;
2
3
  export declare function getConvexTeamDashboardUrl(connection: ConvexTeamConnectionData): string;
3
4
  export declare function getConvexProjectDashboardUrl(project: ConvexProjectData): string;
4
5
  export declare function formatConvexTeam(connection: ConvexTeamConnectionData): string;
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatConvexInviteTimestamp = formatConvexInviteTimestamp;
3
4
  exports.getConvexTeamDashboardUrl = getConvexTeamDashboardUrl;
4
5
  exports.getConvexProjectDashboardUrl = getConvexProjectDashboardUrl;
5
6
  exports.formatConvexTeam = formatConvexTeam;
@@ -10,10 +11,14 @@ exports.logNoConvexProject = logNoConvexProject;
10
11
  exports.confirmRecentConvexInviteAsync = confirmRecentConvexInviteAsync;
11
12
  const tslib_1 = require("tslib");
12
13
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
14
+ const dateformat_1 = tslib_1.__importDefault(require("dateformat"));
13
15
  const log_1 = tslib_1.__importStar(require("../log"));
14
16
  const prompts_1 = require("../prompts");
15
17
  const CONVEX_DASHBOARD_HOST = 'https://dashboard.convex.dev';
16
18
  const RECENT_INVITE_THRESHOLD_MS = 60 * 60 * 1000;
19
+ function formatConvexInviteTimestamp(timestamp) {
20
+ return (0, dateformat_1.default)(timestamp, 'mmm dd HH:MM:ss');
21
+ }
17
22
  function getConvexTeamDashboardUrl(connection) {
18
23
  return `${CONVEX_DASHBOARD_HOST}/t/${encodeURIComponent(connection.convexTeamSlug)}`;
19
24
  }
@@ -27,12 +32,13 @@ function formatConvexTeamConnection(connection) {
27
32
  const lines = [
28
33
  `${chalk_1.default.bold('Team')}: ${formatConvexTeam(connection)}`,
29
34
  `${chalk_1.default.bold('Dashboard')}: ${(0, log_1.link)(getConvexTeamDashboardUrl(connection), { dim: false })}`,
35
+ `${chalk_1.default.bold('Claimed')}: ${connection.hasBeenClaimed ? 'Yes' : 'No'}`,
30
36
  ];
31
37
  if (connection.invitedEmail) {
32
38
  lines.push(`${chalk_1.default.bold('Invited email')}: ${connection.invitedEmail}`);
33
39
  }
34
40
  if (connection.invitedAt) {
35
- lines.push(`${chalk_1.default.bold('Invited at')}: ${connection.invitedAt}`);
41
+ lines.push(`${chalk_1.default.bold('Invited at')}: ${formatConvexInviteTimestamp(connection.invitedAt)}`);
36
42
  }
37
43
  return lines.join('\n');
38
44
  }
@@ -60,7 +66,7 @@ async function confirmRecentConvexInviteAsync(connection, { nonInteractive }) {
60
66
  if (timeSinceInviteMs >= RECENT_INVITE_THRESHOLD_MS) {
61
67
  return true;
62
68
  }
63
- const previousInvite = `A Convex team invite was already sent${connection.invitedEmail ? ` to ${connection.invitedEmail}` : ''} at ${connection.invitedAt}.`;
69
+ const previousInvite = `A Convex team invite was already sent${connection.invitedEmail ? ` to ${connection.invitedEmail}` : ''} at ${formatConvexInviteTimestamp(connection.invitedAt)}.`;
64
70
  if (nonInteractive) {
65
71
  log_1.default.warn(`${previousInvite} Sending another invite because this command is running in non-interactive mode.`);
66
72
  return true;
@@ -8,6 +8,7 @@ export default class BuildDev extends EasCommand {
8
8
  profile: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
9
9
  'skip-build-if-not-found': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
10
  'skip-bundler': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ simulator: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
12
  };
12
13
  static contextDefinition: {
13
14
  projectId: import("../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
@@ -10,6 +10,7 @@ const evaluateConfigWithEnvVarsAsync_1 = require("../../build/evaluateConfigWith
10
10
  const runBuildAndSubmit_1 = require("../../build/runBuildAndSubmit");
11
11
  const repository_1 = require("../../build/utils/repository");
12
12
  const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
13
+ const buildFlags_1 = require("../../commandUtils/buildFlags");
13
14
  const cli_1 = require("../../fingerprint/cli");
14
15
  const generated_1 = require("../../graphql/generated");
15
16
  const BuildQuery_1 = require("../../graphql/queries/BuildQuery");
@@ -42,6 +43,10 @@ class BuildDev extends EasCommand_1.default {
42
43
  description: 'Install and run the development build without starting the bundler server.',
43
44
  default: false,
44
45
  }),
46
+ simulator: core_1.Flags.string({
47
+ description: 'iOS simulator name or UDID to install and run the development build on. If no value is provided, you will be prompted to select a simulator.',
48
+ multiple: false,
49
+ }),
45
50
  };
46
51
  static contextDefinition = {
47
52
  ...this.ContextOptions.LoggedIn,
@@ -52,7 +57,7 @@ class BuildDev extends EasCommand_1.default {
52
57
  ...this.ContextOptions.ProjectId,
53
58
  };
54
59
  async runAsync() {
55
- const { flags } = await this.parse(BuildDev);
60
+ const { flags } = await this.parse(BuildDev, (0, buildFlags_1.preprocessBuildCommandArgs)(this.argv));
56
61
  const { loggedIn: { actor, graphqlClient }, getDynamicPrivateProjectConfigAsync, projectDir, analytics, vcsClient, projectId, } = await this.getContextAsync(BuildDev, {
57
62
  nonInteractive: false,
58
63
  withServerSideEnvironment: null,
@@ -61,6 +66,10 @@ class BuildDev extends EasCommand_1.default {
61
66
  if (process.platform !== 'darwin' && platform === eas_build_job_1.Platform.IOS) {
62
67
  core_1.Errors.error('Running iOS builds in simulator is only supported on macOS.', { exit: 1 });
63
68
  }
69
+ if (flags.simulator && platform !== eas_build_job_1.Platform.IOS) {
70
+ core_1.Errors.error('The --simulator flag can only be used with --platform ios.', { exit: 1 });
71
+ }
72
+ const simulator = flags.simulator === '' ? true : flags.simulator;
64
73
  await vcsClient.ensureRepoExistsAsync();
65
74
  await (0, repository_1.ensureRepoIsCleanAsync)(vcsClient, flags.nonInteractive);
66
75
  await (0, configure_1.ensureProjectConfiguredAsync)({
@@ -103,7 +112,7 @@ class BuildDev extends EasCommand_1.default {
103
112
  const build = builds[0];
104
113
  log_1.default.succeed(`🎯 Found successful build with matching fingerprint on EAS servers. Running it...`);
105
114
  if (build.artifacts?.applicationArchiveUrl) {
106
- await (0, runBuildAndSubmit_1.downloadAndRunAsync)(build);
115
+ await (0, runBuildAndSubmit_1.downloadAndRunAsync)(build, simulator);
107
116
  if (!flags['skip-bundler']) {
108
117
  await this.startDevServerAsync({ projectDir, platform });
109
118
  }
@@ -143,6 +152,7 @@ class BuildDev extends EasCommand_1.default {
143
152
  autoSubmit: false,
144
153
  localBuildOptions: {},
145
154
  profile: flags.profile ?? DEFAULT_EAS_BUILD_RUN_PROFILE_NAME,
155
+ simulator,
146
156
  },
147
157
  actor,
148
158
  getDynamicPrivateProjectConfigAsync,
@@ -10,6 +10,7 @@ export default class Run extends EasCommand {
10
10
  id: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
11
  platform: import("@oclif/core/lib/interfaces").OptionFlag<"android" | "ios" | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
12
  profile: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ simulator: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
14
  };
14
15
  static contextDefinition: {
15
16
  vcsClient: import("../../commandUtils/context/VcsClientContextField").default;
@@ -6,6 +6,7 @@ const assert_1 = tslib_1.__importDefault(require("assert"));
6
6
  const fs_extra_1 = require("fs-extra");
7
7
  const queries_1 = require("../../build/queries");
8
8
  const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
9
+ const buildFlags_1 = require("../../commandUtils/buildFlags");
9
10
  const pagination_1 = require("../../commandUtils/pagination");
10
11
  const generated_1 = require("../../graphql/generated");
11
12
  const BuildQuery_1 = require("../../graphql/queries/BuildQuery");
@@ -44,6 +45,10 @@ class Run extends EasCommand_1.default {
44
45
  description: 'Name of the build profile used to create the build to run. When specified, only builds created with the specified build profile will be queried.',
45
46
  helpValue: 'PROFILE_NAME',
46
47
  }),
48
+ simulator: core_1.Flags.string({
49
+ description: 'iOS simulator name or UDID to install and run the build on. If no value is provided, you will be prompted to select a simulator.',
50
+ multiple: false,
51
+ }),
47
52
  ...pagination_1.EasPaginatedQueryFlags,
48
53
  };
49
54
  static contextDefinition = {
@@ -52,23 +57,26 @@ class Run extends EasCommand_1.default {
52
57
  ...this.ContextOptions.Vcs,
53
58
  };
54
59
  async runAsync() {
55
- const { flags: rawFlags } = await this.parse(Run);
60
+ const { flags: rawFlags } = await this.parse(Run, (0, buildFlags_1.preprocessBuildCommandArgs)(this.argv));
56
61
  const flags = await this.sanitizeFlagsAsync(rawFlags);
57
62
  const queryOptions = (0, pagination_1.getPaginatedQueryOptions)(flags);
58
63
  const { loggedIn: { graphqlClient }, projectId, } = await this.getContextAsync(Run, {
59
64
  nonInteractive: false,
60
65
  });
61
66
  const simulatorBuildPath = await getPathToSimulatorBuildAppAsync(graphqlClient, projectId, flags, queryOptions);
62
- await (0, run_1.runAsync)(simulatorBuildPath, flags.selectedPlatform);
67
+ await (0, run_1.runAsync)(simulatorBuildPath, flags.selectedPlatform, flags.simulator);
63
68
  }
64
69
  async sanitizeFlagsAsync(flags) {
65
- const { platform, limit, offset, profile, ...runArchiveFlags } = flags;
70
+ const { platform, limit, offset, profile, simulator, ...runArchiveFlags } = flags;
66
71
  const selectedPlatform = await resolvePlatformAsync(platform);
67
72
  if (platform === 'ios' && process.platform !== 'darwin') {
68
73
  core_1.Errors.error('You can only use an iOS simulator to run apps on macOS devices', {
69
74
  exit: 1,
70
75
  });
71
76
  }
77
+ if (simulator && selectedPlatform !== generated_1.AppPlatform.Ios) {
78
+ core_1.Errors.error('The --simulator flag can only be used with --platform ios.', { exit: 1 });
79
+ }
72
80
  if (runArchiveFlags.path &&
73
81
  !((runArchiveFlags.path.endsWith('.tar.gz') ||
74
82
  runArchiveFlags.path.endsWith('.app') ||
@@ -87,6 +95,7 @@ class Run extends EasCommand_1.default {
87
95
  limit,
88
96
  offset,
89
97
  profile,
98
+ simulator: simulator === '' ? true : simulator,
90
99
  };
91
100
  }
92
101
  }
@@ -12,6 +12,7 @@ export default class IntegrationsConvexConnect extends EasCommand {
12
12
  'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
13
13
  };
14
14
  runAsync(): Promise<void>;
15
+ private upsertConvexUrlEasEnvVarAsync;
15
16
  private resolveRegionAsync;
16
17
  private resolveTeamNameAsync;
17
18
  private resolveProjectNameAsync;
@@ -7,11 +7,15 @@ const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
7
  const dotenv_1 = tslib_1.__importDefault(require("dotenv"));
8
8
  const fs = tslib_1.__importStar(require("fs-extra"));
9
9
  const path_1 = tslib_1.__importDefault(require("path"));
10
+ const environment_1 = require("../../../build/utils/environment");
10
11
  const EasCommand_1 = tslib_1.__importDefault(require("../../../commandUtils/EasCommand"));
11
12
  const convex_1 = require("../../../commandUtils/convex");
12
13
  const flags_1 = require("../../../commandUtils/flags");
14
+ const generated_1 = require("../../../graphql/generated");
13
15
  const ConvexMutation_1 = require("../../../graphql/mutations/ConvexMutation");
16
+ const EnvironmentVariableMutation_1 = require("../../../graphql/mutations/EnvironmentVariableMutation");
14
17
  const ConvexQuery_1 = require("../../../graphql/queries/ConvexQuery");
18
+ const EnvironmentVariablesQuery_1 = require("../../../graphql/queries/EnvironmentVariablesQuery");
15
19
  const log_1 = tslib_1.__importDefault(require("../../../log"));
16
20
  const ora_1 = require("../../../ora");
17
21
  const projectUtils_1 = require("../../../project/projectUtils");
@@ -21,6 +25,12 @@ const CONVEX_REGIONS = [
21
25
  { title: 'EU West (aws-eu-west-1)', value: 'aws-eu-west-1' },
22
26
  ];
23
27
  const DEFAULT_REGION = 'aws-us-east-1';
28
+ const EAS_CONVEX_ENV_VAR_NAME = 'EXPO_PUBLIC_CONVEX_URL';
29
+ const EAS_CONVEX_ENVIRONMENTS = [
30
+ environment_1.DefaultEnvironment.Production,
31
+ environment_1.DefaultEnvironment.Preview,
32
+ environment_1.DefaultEnvironment.Development,
33
+ ];
24
34
  class IntegrationsConvexConnect extends EasCommand_1.default {
25
35
  static description = 'connect Convex to your Expo project';
26
36
  static contextDefinition = {
@@ -99,21 +109,63 @@ class IntegrationsConvexConnect extends EasCommand_1.default {
99
109
  spinner.fail('Failed to set up Convex project');
100
110
  throw error;
101
111
  }
102
- // 6. Send team invite (non-fatal)
103
- await this.sendTeamInviteAsync(graphqlClient, connection, actor, { nonInteractive });
104
- // 7. Write deploy key and URL to .env.local
112
+ // 6. Save the Convex URL as an EAS environment variable for builds
113
+ await this.upsertConvexUrlEasEnvVarAsync(graphqlClient, projectId, setupResult.convexDeploymentUrl, nonInteractive);
114
+ // 7. Send team invite (non-fatal)
115
+ const teamInviteResult = await this.sendTeamInviteAsync(graphqlClient, connection, actor, {
116
+ nonInteractive,
117
+ });
118
+ // 8. Write deploy key and URL to .env.local
105
119
  await this.writeEnvLocalAsync(projectDir, setupResult.deployKey, setupResult.convexDeploymentUrl, nonInteractive);
106
- // 8. Success message
120
+ // 9. Success message
107
121
  log_1.default.addNewLineIfNone();
108
122
  log_1.default.log(chalk_1.default.green('Convex is ready!'));
109
123
  log_1.default.newLine();
110
124
  log_1.default.log('Next steps:');
111
125
  log_1.default.log(` 1. Start the Convex dev server: ${chalk_1.default.cyan('npx convex dev')}`);
126
+ log_1.default.log(` 2. Learn how to connect to your new Convex database by following our quickstart guide: ${chalk_1.default.cyan('https://docs.expo.dev/guides/using-convex')}`);
127
+ log_1.default.log(` 3. Read more about Convex: ${chalk_1.default.cyan('https://docs.convex.dev/')}`);
112
128
  log_1.default.newLine();
113
- if (this.getActorEmail(actor)) {
129
+ if (teamInviteResult === 'sent') {
114
130
  log_1.default.log(`Check your email for an invitation to join your Convex team. Accept it for full dashboard access.`);
115
131
  }
116
132
  }
133
+ async upsertConvexUrlEasEnvVarAsync(graphqlClient, projectId, convexUrl, nonInteractive) {
134
+ const existingVariables = await EnvironmentVariablesQuery_1.EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
135
+ appId: projectId,
136
+ filterNames: [EAS_CONVEX_ENV_VAR_NAME],
137
+ });
138
+ const existingProjectVariable = existingVariables.find(variable => variable.scope === generated_1.EnvironmentVariableScope.Project);
139
+ if (existingProjectVariable) {
140
+ if (!nonInteractive) {
141
+ const overwrite = await (0, prompts_1.confirmAsync)({
142
+ message: `EAS already has an ${EAS_CONVEX_ENV_VAR_NAME} environment variable for this project. Overwrite it?`,
143
+ });
144
+ if (!overwrite) {
145
+ log_1.default.warn(`Skipped updating EAS environment variable ${chalk_1.default.bold(EAS_CONVEX_ENV_VAR_NAME)}.`);
146
+ return;
147
+ }
148
+ }
149
+ await EnvironmentVariableMutation_1.EnvironmentVariableMutation.updateAsync(graphqlClient, {
150
+ id: existingProjectVariable.id,
151
+ name: EAS_CONVEX_ENV_VAR_NAME,
152
+ value: convexUrl,
153
+ environments: EAS_CONVEX_ENVIRONMENTS,
154
+ visibility: generated_1.EnvironmentVariableVisibility.Public,
155
+ type: generated_1.EnvironmentSecretType.String,
156
+ });
157
+ log_1.default.withTick(`Updated EAS environment variable ${chalk_1.default.bold(EAS_CONVEX_ENV_VAR_NAME)} for builds`);
158
+ return;
159
+ }
160
+ await EnvironmentVariableMutation_1.EnvironmentVariableMutation.createForAppAsync(graphqlClient, {
161
+ name: EAS_CONVEX_ENV_VAR_NAME,
162
+ value: convexUrl,
163
+ environments: EAS_CONVEX_ENVIRONMENTS,
164
+ visibility: generated_1.EnvironmentVariableVisibility.Public,
165
+ type: generated_1.EnvironmentSecretType.String,
166
+ }, projectId);
167
+ log_1.default.withTick(`Created EAS environment variable ${chalk_1.default.bold(EAS_CONVEX_ENV_VAR_NAME)} for builds`);
168
+ }
117
169
  async resolveRegionAsync(flagValue, nonInteractive) {
118
170
  if (flagValue) {
119
171
  return flagValue;
@@ -169,24 +221,30 @@ class IntegrationsConvexConnect extends EasCommand_1.default {
169
221
  return actor.__typename === 'User' ? actor.email : null;
170
222
  }
171
223
  async sendTeamInviteAsync(graphqlClient, connection, actor, { nonInteractive }) {
224
+ if (connection.hasBeenClaimed) {
225
+ log_1.default.warn('Convex team has already been claimed. Skipping Convex team invitation.');
226
+ return 'skipped';
227
+ }
172
228
  const email = this.getActorEmail(actor);
173
229
  if (!email) {
174
230
  log_1.default.warn(`Could not determine your verified email address, so no Convex team invitation was sent. Run ${chalk_1.default.cyan('eas integrations:convex:team:invite')} after signing in with a user account.`);
175
- return;
231
+ return 'skipped';
176
232
  }
177
233
  if (!(await (0, convex_1.confirmRecentConvexInviteAsync)(connection, { nonInteractive }))) {
178
234
  log_1.default.warn('Skipped sending Convex team invitation.');
179
- return;
235
+ return 'skipped';
180
236
  }
181
237
  try {
182
238
  await ConvexMutation_1.ConvexMutation.sendConvexTeamInviteToVerifiedEmailAsync(graphqlClient, {
183
239
  convexTeamConnectionId: connection.id,
184
240
  });
185
241
  log_1.default.withTick(`Sent Convex team invitation to ${chalk_1.default.bold(email)}`);
242
+ return 'sent';
186
243
  }
187
244
  catch (error) {
188
245
  log_1.default.warn(`Failed to send Convex team invitation to ${email}. Run ${chalk_1.default.cyan('eas integrations:convex:team:invite')} to retry.`);
189
246
  log_1.default.warn(error instanceof Error ? error.message : String(error));
247
+ return 'failed';
190
248
  }
191
249
  }
192
250
  async installConvexPackageAsync(projectDir) {
@@ -48,6 +48,10 @@ class IntegrationsConvexTeamInvite extends EasCommand_1.default {
48
48
  this.logTeam(connection);
49
49
  this.logPreviousInvite(connection);
50
50
  log_1.default.newLine();
51
+ if (connection.hasBeenClaimed) {
52
+ log_1.default.warn('Convex team has already been claimed. Skipping Convex team invitation.');
53
+ return;
54
+ }
51
55
  if (!(await (0, convex_1.confirmRecentConvexInviteAsync)(connection, { nonInteractive }))) {
52
56
  log_1.default.warn('Skipped sending Convex team invitation.');
53
57
  return;
@@ -71,6 +75,7 @@ class IntegrationsConvexTeamInvite extends EasCommand_1.default {
71
75
  logTeam(connection) {
72
76
  log_1.default.log(chalk_1.default.bold(`Convex team ${(0, convex_1.formatConvexTeam)(connection)}`));
73
77
  log_1.default.log(`${chalk_1.default.bold('Dashboard')}: ${(0, log_1.link)((0, convex_1.getConvexTeamDashboardUrl)(connection), { dim: false })}`);
78
+ log_1.default.log(`${chalk_1.default.bold('Claimed')}: ${connection.hasBeenClaimed ? 'Yes' : 'No'}`);
74
79
  }
75
80
  logPreviousInvite(connection) {
76
81
  if (!connection.invitedEmail && !connection.invitedAt) {
@@ -82,7 +87,7 @@ class IntegrationsConvexTeamInvite extends EasCommand_1.default {
82
87
  log_1.default.log(`${chalk_1.default.bold('Email')}: ${connection.invitedEmail}`);
83
88
  }
84
89
  if (connection.invitedAt) {
85
- log_1.default.log(`${chalk_1.default.bold('Sent at')}: ${connection.invitedAt}`);
90
+ log_1.default.log(`${chalk_1.default.bold('Sent at')}: ${(0, convex_1.formatConvexInviteTimestamp)(connection.invitedAt)}`);
86
91
  }
87
92
  }
88
93
  async resolveConnectionAsync(connections, team, nonInteractive) {
@@ -6,11 +6,12 @@ const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasComm
6
6
  const errors_1 = require("../../commandUtils/errors");
7
7
  const flags_1 = require("../../commandUtils/flags");
8
8
  const pagination_1 = require("../../commandUtils/pagination");
9
- const generated_1 = require("../../graphql/generated");
10
9
  const log_1 = tslib_1.__importDefault(require("../../log"));
11
10
  const fetchEvents_1 = require("../../observe/fetchEvents");
12
11
  const metricNames_1 = require("../../observe/metricNames");
13
12
  const formatEvents_1 = require("../../observe/formatEvents");
13
+ const platforms_1 = require("../../observe/platforms");
14
+ const resolveProjectContext_1 = require("../../observe/resolveProjectContext");
14
15
  const startAndEndTime_1 = require("../../observe/startAndEndTime");
15
16
  const prompts_1 = require("../../prompts");
16
17
  const json_1 = require("../../utils/json");
@@ -34,7 +35,7 @@ class ObserveEvents extends EasCommand_1.default {
34
35
  })(),
35
36
  platform: core_1.Flags.option({
36
37
  description: 'Filter by platform',
37
- options: Object.values(generated_1.AppObservePlatform).map(s => s.toLowerCase()),
38
+ options: platforms_1.allowedPlatformFlagValues,
38
39
  })(),
39
40
  after: core_1.Flags.string({
40
41
  description: 'Cursor for pagination. Use the endCursor from a previous query to fetch the next page.',
@@ -76,20 +77,13 @@ class ObserveEvents extends EasCommand_1.default {
76
77
  };
77
78
  async runAsync() {
78
79
  const { flags, args } = await this.parse(ObserveEvents);
79
- let projectId;
80
- let graphqlClient;
81
- if (flags['project-id']) {
82
- projectId = flags['project-id'];
83
- const ctx = await this.getContextAsync({ contextDefinition: ObserveEvents.loggedInOnlyContextDefinition }, { nonInteractive: flags['non-interactive'] });
84
- graphqlClient = ctx.loggedIn.graphqlClient;
85
- }
86
- else {
87
- const ctx = await this.getContextAsync(ObserveEvents, {
88
- nonInteractive: flags['non-interactive'],
89
- });
90
- projectId = ctx.projectId;
91
- graphqlClient = ctx.loggedIn.graphqlClient;
92
- }
80
+ const { projectId, graphqlClient } = await (0, resolveProjectContext_1.resolveObserveCommandContextAsync)({
81
+ command: this,
82
+ commandClass: ObserveEvents,
83
+ loggedInOnlyContextDefinition: ObserveEvents.loggedInOnlyContextDefinition,
84
+ projectIdOverride: flags['project-id'],
85
+ nonInteractive: flags['non-interactive'],
86
+ });
93
87
  if (flags.json) {
94
88
  (0, json_1.enableJsonOutput)();
95
89
  }
@@ -113,14 +107,8 @@ class ObserveEvents extends EasCommand_1.default {
113
107
  }
114
108
  const orderBy = (0, fetchEvents_1.resolveOrderBy)(flags.sort);
115
109
  const { daysBack, startTime, endTime } = (0, startAndEndTime_1.resolveTimeRange)(flags);
116
- const platform = flags.platform
117
- ? flags.platform === 'android'
118
- ? generated_1.AppObservePlatform.Android
119
- : generated_1.AppObservePlatform.Ios
120
- : undefined;
121
- const platforms = platform
122
- ? [platform === generated_1.AppObservePlatform.Android ? generated_1.AppPlatform.Android : generated_1.AppPlatform.Ios]
123
- : [generated_1.AppPlatform.Android, generated_1.AppPlatform.Ios];
110
+ const platform = (0, platforms_1.appObservePlatformFromFlag)(flags.platform);
111
+ const platforms = (0, platforms_1.appPlatformsFromFlag)(flags.platform);
124
112
  const [{ events, pageInfo }, totalEventCount] = await Promise.all([
125
113
  (0, fetchEvents_1.fetchObserveEventsAsync)(graphqlClient, projectId, {
126
114
  metricName,
@@ -0,0 +1,29 @@
1
+ import EasCommand from '../../commandUtils/EasCommand';
2
+ export default class ObserveLogs extends EasCommand {
3
+ static hidden: boolean;
4
+ static description: string;
5
+ static args: {
6
+ eventName: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ platform: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ after: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ limit: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined>;
14
+ start: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
15
+ end: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
16
+ days: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
+ 'app-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
18
+ 'update-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
+ 'session-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ 'all-events': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
21
+ 'project-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
+ };
23
+ static contextDefinition: {
24
+ loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
25
+ projectId: import("../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
26
+ };
27
+ private static loggedInOnlyContextDefinition;
28
+ runAsync(): Promise<void>;
29
+ }