eas-cli 16.31.0 → 18.0.1

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 (59) hide show
  1. package/README.md +116 -91
  2. package/build/branch/queries.js +6 -1
  3. package/build/build/createContext.js +5 -3
  4. package/build/build/queries.js +13 -1
  5. package/build/build/runBuildAndSubmit.js +2 -0
  6. package/build/build/utils/devClient.d.ts +1 -0
  7. package/build/build/utils/devClient.js +3 -2
  8. package/build/build/utils/repository.js +3 -3
  9. package/build/build/utils/resourceClass.d.ts +1 -1
  10. package/build/build/utils/resourceClass.js +2 -2
  11. package/build/channel/queries.js +10 -1
  12. package/build/commandUtils/context/contextUtils/createGraphqlClient.d.ts +1 -5
  13. package/build/commandUtils/new/templates/AGENTS.md +0 -1
  14. package/build/commandUtils/usageUtils.d.ts +58 -0
  15. package/build/commandUtils/usageUtils.js +204 -0
  16. package/build/commands/account/login.d.ts +1 -0
  17. package/build/commands/account/login.js +7 -2
  18. package/build/commands/account/usage.d.ts +16 -0
  19. package/build/commands/account/usage.js +326 -0
  20. package/build/commands/env/create.js +5 -2
  21. package/build/commands/env/list.js +1 -5
  22. package/build/commands/update/index.js +2 -2
  23. package/build/commands/upload.js +3 -3
  24. package/build/credentials/ios/api/graphql/mutations/AppleDistributionCertificateMutation.js +1 -3
  25. package/build/devices/queries.js +23 -2
  26. package/build/graphql/generated.d.ts +1063 -39
  27. package/build/graphql/generated.js +78 -11
  28. package/build/graphql/mutations/EnvironmentVariableMutation.js +1 -4
  29. package/build/graphql/mutations/PublishMutation.js +1 -4
  30. package/build/graphql/mutations/UserPreferencesMutation.js +1 -3
  31. package/build/graphql/mutations/WorkflowRevisionMutation.js +2 -10
  32. package/build/graphql/queries/AccountQuery.d.ts +15 -0
  33. package/build/graphql/queries/AccountQuery.js +197 -0
  34. package/build/graphql/queries/AppVersionQuery.js +1 -5
  35. package/build/graphql/types/Account.d.ts +11 -0
  36. package/build/graphql/types/Account.js +142 -1
  37. package/build/log.js +1 -1
  38. package/build/ora.js +1 -1
  39. package/build/project/discourageExpoGoForProdAsync.d.ts +4 -0
  40. package/build/project/discourageExpoGoForProdAsync.js +46 -0
  41. package/build/rollout/actions/ManageRollout.js +5 -1
  42. package/build/rollout/actions/SelectRuntime.js +10 -1
  43. package/build/update/queries.js +28 -2
  44. package/build/user/SessionManager.d.ts +3 -2
  45. package/build/user/SessionManager.js +6 -6
  46. package/build/user/expoBrowserAuthFlowLauncher.d.ts +3 -0
  47. package/build/user/{expoSsoLauncher.js → expoBrowserAuthFlowLauncher.js} +21 -47
  48. package/build/user/fetchSessionSecretAndUserFromBrowserAuthFlow.d.ts +7 -0
  49. package/build/user/fetchSessionSecretAndUserFromBrowserAuthFlow.js +14 -0
  50. package/build/utils/prompts.js +3 -3
  51. package/build/utils/usage/checkForOverages.js +6 -4
  52. package/build/worker/mutations.js +2 -8
  53. package/oclif.manifest.json +40 -1
  54. package/package.json +60 -63
  55. package/build/graphql/queries/AccountUsageQuery.d.ts +0 -5
  56. package/build/graphql/queries/AccountUsageQuery.js +0 -40
  57. package/build/user/expoSsoLauncher.d.ts +0 -3
  58. package/build/user/fetchSessionSecretAndSsoUser.d.ts +0 -5
  59. package/build/user/fetchSessionSecretAndSsoUser.js +0 -17
@@ -20,7 +20,12 @@ const queries_1 = require("../utils/queries");
20
20
  exports.BRANCHES_LIMIT = 50;
21
21
  async function selectBranchOnAppAsync(graphqlClient, { projectId, promptTitle, displayTextForListItem, paginatedQueryOptions, }) {
22
22
  if (paginatedQueryOptions.nonInteractive) {
23
- throw new Error('Unable to select a branch in non-interactive mode.');
23
+ const branches = await queryBranchesOnProjectAsync(graphqlClient, paginatedQueryOptions.limit ?? exports.BRANCHES_LIMIT, paginatedQueryOptions.offset, projectId);
24
+ if (branches.length === 0) {
25
+ throw new Error(`No branches found for project "${projectId}".`);
26
+ }
27
+ const branchList = branches.map(b => b.name).join('\n ');
28
+ throw new Error(`Unable to select a branch in non-interactive mode. Use the --branch flag to specify the branch. Available branches:\n ${branchList}`);
24
29
  }
25
30
  const selectedBranch = await (0, queries_1.paginatedQueryWithSelectPromptAsync)({
26
31
  limit: paginatedQueryOptions.limit ?? exports.BRANCHES_LIMIT,
@@ -21,8 +21,10 @@ async function createBuildContextAsync({ buildProfileName, buildProfile, easJson
21
21
  env,
22
22
  });
23
23
  const projectName = exp.slug;
24
- const account = await (0, projectUtils_1.getOwnerAccountForProjectIdAsync)(graphqlClient, projectId);
25
- const workflow = await (0, workflow_1.resolveWorkflowAsync)(projectDir, platform, vcsClient);
24
+ const [account, workflow] = await Promise.all([
25
+ (0, projectUtils_1.getOwnerAccountForProjectIdAsync)(graphqlClient, projectId),
26
+ (0, workflow_1.resolveWorkflowAsync)(projectDir, platform, vcsClient),
27
+ ]);
26
28
  const accountId = account.id;
27
29
  const runFromCI = getenv_1.default.boolish('CI', false);
28
30
  const developmentClient = buildProfile.developmentClient ??
@@ -61,7 +63,7 @@ async function createBuildContextAsync({ buildProfileName, buildProfile, easJson
61
63
  local: localBuildOptions.localBuildMode === local_1.LocalBuildMode.LOCAL_BUILD_PLUGIN,
62
64
  };
63
65
  analytics.logEvent(AnalyticsManager_1.BuildEvent.BUILD_COMMAND, analyticsEventProperties);
64
- const resourceClass = await (0, resourceClass_1.resolveBuildResourceClassAsync)(buildProfile, platform, resourceClassFlag);
66
+ const resourceClass = (0, resourceClass_1.resolveBuildResourceClass)(buildProfile, platform, resourceClassFlag);
65
67
  const commonContext = {
66
68
  accountId,
67
69
  accountName: account.name,
@@ -45,7 +45,19 @@ async function listAndRenderBuildsOnAppAsync(graphqlClient, { projectId, project
45
45
  }
46
46
  async function listAndSelectBuildOnAppAsync(graphqlClient, { projectId, title, filter, paginatedQueryOptions, selectPromptDisabledFunction, selectPromptWarningMessage, }) {
47
47
  if (paginatedQueryOptions.nonInteractive) {
48
- throw new Error('Unable to select a build in non-interactive mode.');
48
+ const builds = await BuildQuery_1.BuildQuery.viewBuildsOnAppAsync(graphqlClient, {
49
+ appId: projectId,
50
+ limit: paginatedQueryOptions.limit ?? exports.BUILDS_LIMIT,
51
+ offset: paginatedQueryOptions.offset,
52
+ filter,
53
+ });
54
+ if (builds.length === 0) {
55
+ throw new Error('No builds found for this project.');
56
+ }
57
+ const buildList = builds
58
+ .map(b => `${b.id} (platform: ${b.platform}, status: ${b.status}${b.buildProfile ? `, profile: ${b.buildProfile}` : ''})`)
59
+ .join('\n ');
60
+ throw new Error(`Unable to select a build in non-interactive mode. Available builds:\n ${buildList}`);
49
61
  }
50
62
  else {
51
63
  const selectedBuild = await (0, queries_1.paginatedQueryWithSelectPromptAsync)({
@@ -27,6 +27,7 @@ const AppPlatform_1 = require("../graphql/types/AppPlatform");
27
27
  const log_1 = tslib_1.__importStar(require("../log"));
28
28
  const platform_1 = require("../platform");
29
29
  const customBuildConfig_1 = require("../project/customBuildConfig");
30
+ const discourageExpoGoForProdAsync_1 = require("../project/discourageExpoGoForProdAsync");
30
31
  const expoSdk_1 = require("../project/expoSdk");
31
32
  const metroConfig_1 = require("../project/metroConfig");
32
33
  const projectUtils_1 = require("../project/projectUtils");
@@ -64,6 +65,7 @@ async function runBuildAndSubmitAsync({ graphqlClient, analytics, vcsClient, pro
64
65
  profileName: flags.profile ?? undefined,
65
66
  projectDir,
66
67
  });
68
+ await (0, discourageExpoGoForProdAsync_1.discourageExpoGoForProdAsync)(buildProfiles, projectDir, vcsClient);
67
69
  for (const buildProfile of buildProfiles) {
68
70
  if (buildProfile.profile.image && ['default', 'stable'].includes(buildProfile.profile.image)) {
69
71
  log_1.default.warn(`The "image" field in the build profile "${buildProfile.profileName}" is set to "${buildProfile.profile.image}". This tag is deprecated and will be removed in the future. Use other images or tags listed here: https://docs.expo.dev/build-reference/infrastructure/`);
@@ -6,3 +6,4 @@ export declare function ensureExpoDevClientInstalledForDevClientBuildsAsync({ pr
6
6
  nonInteractive?: boolean;
7
7
  buildProfiles?: ProfileData<'build'>[];
8
8
  }): Promise<void>;
9
+ export declare function isExpoDevClientInstalled(projectDir: string): boolean;
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.ensureExpoDevClientInstalledForDevClientBuildsAsync = ensureExpoDevClientInstalledForDevClientBuildsAsync;
4
+ exports.isExpoDevClientInstalled = isExpoDevClientInstalled;
4
5
  const tslib_1 = require("tslib");
5
6
  const eas_build_job_1 = require("@expo/eas-build-job");
6
7
  const core_1 = require("@oclif/core");
@@ -14,7 +15,7 @@ const workflow_1 = require("../../project/workflow");
14
15
  const prompts_1 = require("../../prompts");
15
16
  const expoCli_1 = require("../../utils/expoCli");
16
17
  async function ensureExpoDevClientInstalledForDevClientBuildsAsync({ projectDir, vcsClient, nonInteractive = false, buildProfiles = [], }) {
17
- if (await isExpoDevClientInstalledAsync(projectDir)) {
18
+ if (isExpoDevClientInstalled(projectDir)) {
18
19
  return;
19
20
  }
20
21
  const buildProfilesWithDevelopmentClientRequired = buildProfiles.filter(buildProfile => buildProfile.profile.developmentClient);
@@ -71,7 +72,7 @@ async function ensureExpoDevClientInstalledForDevClientBuildsAsync({ projectDir,
71
72
  }
72
73
  }
73
74
  }
74
- async function isExpoDevClientInstalledAsync(projectDir) {
75
+ function isExpoDevClientInstalled(projectDir) {
75
76
  try {
76
77
  (0, resolve_from_1.default)(projectDir, 'expo-dev-client/package.json');
77
78
  return true;
@@ -12,7 +12,7 @@ const tslib_1 = require("tslib");
12
12
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
13
13
  const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
14
14
  const path_1 = tslib_1.__importDefault(require("path"));
15
- const tar_1 = tslib_1.__importDefault(require("tar"));
15
+ const tar = tslib_1.__importStar(require("tar"));
16
16
  const uuid_1 = require("uuid");
17
17
  const log_1 = tslib_1.__importStar(require("../../log"));
18
18
  const ora_1 = require("../../ora");
@@ -78,7 +78,7 @@ async function makeProjectMetadataFileAsync(archivePath) {
78
78
  await fs_extra_1.default.mkdirp((0, paths_1.getTmpDirectory)());
79
79
  const metadataLocation = path_1.default.join((0, paths_1.getTmpDirectory)(), `${(0, uuid_1.v4)()}-eas-build-metadata.json`);
80
80
  const archiveContent = [];
81
- await tar_1.default.list({
81
+ await tar.list({
82
82
  file: archivePath,
83
83
  onentry: (entry) => {
84
84
  if (entry.type === 'File' && !entry.path.includes('.git/')) {
@@ -108,7 +108,7 @@ async function makeProjectTarballAsync(vcsClient) {
108
108
  (0, timer_1.startTimer)(compressTimerLabel);
109
109
  try {
110
110
  await vcsClient.makeShallowCopyAsync(shallowClonePath);
111
- await tar_1.default.create({ cwd: shallowClonePath, file: tarPath, prefix: 'project', gzip: true }, [
111
+ await tar.create({ cwd: shallowClonePath, file: tarPath, prefix: 'project', gzip: true }, [
112
112
  '.',
113
113
  ]);
114
114
  }
@@ -1,4 +1,4 @@
1
1
  import { Platform } from '@expo/eas-build-job';
2
2
  import { BuildProfile, ResourceClass } from '@expo/eas-json';
3
3
  import { BuildResourceClass } from '../../graphql/generated';
4
- export declare function resolveBuildResourceClassAsync<T extends Platform>(profile: BuildProfile<T>, platform: Platform, resourceClassFlag?: ResourceClass): Promise<BuildResourceClass>;
4
+ export declare function resolveBuildResourceClass<T extends Platform>(profile: BuildProfile<T>, platform: Platform, resourceClassFlag?: ResourceClass): BuildResourceClass;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resolveBuildResourceClassAsync = resolveBuildResourceClassAsync;
3
+ exports.resolveBuildResourceClass = resolveBuildResourceClass;
4
4
  const tslib_1 = require("tslib");
5
5
  const eas_build_job_1 = require("@expo/eas-build-job");
6
6
  const eas_json_1 = require("@expo/eas-json");
@@ -20,7 +20,7 @@ const androidResourceClassToBuildResourceClassMapping = {
20
20
  [eas_json_1.ResourceClass.LARGE]: generated_1.BuildResourceClass.AndroidLarge,
21
21
  [eas_json_1.ResourceClass.MEDIUM]: generated_1.BuildResourceClass.AndroidMedium,
22
22
  };
23
- async function resolveBuildResourceClassAsync(profile, platform, resourceClassFlag) {
23
+ function resolveBuildResourceClass(profile, platform, resourceClassFlag) {
24
24
  const profileResourceClass = profile.resourceClass;
25
25
  if (profileResourceClass && resourceClassFlag && resourceClassFlag !== profileResourceClass) {
26
26
  log_1.default.warn(`Build profile specifies the "${profileResourceClass}" resource class but you passed "${resourceClassFlag}" to --resource-class.\nUsing "${resourceClassFlag}" as the override.`);
@@ -28,7 +28,16 @@ const queries_2 = require("../utils/queries");
28
28
  exports.CHANNELS_LIMIT = 25;
29
29
  async function selectChannelOnAppAsync(graphqlClient, { projectId, selectionPromptTitle, paginatedQueryOptions, }) {
30
30
  if (paginatedQueryOptions.nonInteractive) {
31
- throw new Error('Unable to select a channel in non-interactive mode.');
31
+ const channels = await queryChannelsOnAppAsync(graphqlClient, {
32
+ appId: projectId,
33
+ limit: paginatedQueryOptions.limit ?? exports.CHANNELS_LIMIT,
34
+ offset: paginatedQueryOptions.offset,
35
+ });
36
+ if (channels.length === 0) {
37
+ throw new Error(`No channels found for project "${projectId}".`);
38
+ }
39
+ const channelList = channels.map(c => c.name).join('\n ');
40
+ throw new Error(`Unable to select a channel in non-interactive mode. Use the --channel flag to specify the channel. Available channels:\n ${channelList}`);
32
41
  }
33
42
  const updateChannel = await (0, queries_2.paginatedQueryWithSelectPromptAsync)({
34
43
  limit: paginatedQueryOptions.limit ?? exports.CHANNELS_LIMIT,
@@ -1,9 +1,5 @@
1
- import { AnyVariables, Client, OperationContext, OperationResult, OperationResultSource, TypedDocumentNode } from '@urql/core';
2
- import { DocumentNode } from 'graphql';
1
+ import { Client } from '@urql/core';
3
2
  export interface ExpoGraphqlClient extends Client {
4
- query<Data = any, Variables extends AnyVariables = AnyVariables>(query: DocumentNode | TypedDocumentNode<Data, Variables> | string, variables: Variables, context: Partial<OperationContext> & {
5
- additionalTypenames: string[];
6
- }): OperationResultSource<OperationResult<Data, Variables>>;
7
3
  }
8
4
  export declare function createGraphqlClient(authInfo: {
9
5
  accessToken: string | null;
@@ -153,7 +153,6 @@ If there are errors in **Expo Go** or the project is not running, create a **dev
153
153
  When working on this project:
154
154
 
155
155
  1. **Always start by consulting the appropriate documentation**:
156
-
157
156
  - For general Expo questions: https://docs.expo.dev/llms-full.txt
158
157
  - For EAS/deployment questions: https://docs.expo.dev/llms-eas.txt
159
158
  - For SDK/API questions: https://docs.expo.dev/llms-sdk.txt
@@ -0,0 +1,58 @@
1
+ import { AppPlatform, EasBuildBillingResourceClass } from '../graphql/generated';
2
+ import { AccountFullUsageData } from '../graphql/queries/AccountQuery';
3
+ export interface UsageMetricDisplay {
4
+ name: string;
5
+ planValue: number;
6
+ limit: number;
7
+ percentUsed: number;
8
+ overageValue: number;
9
+ overageCost: number;
10
+ unit?: string;
11
+ }
12
+ export interface BuildOverageByWorkerSize {
13
+ platform: AppPlatform;
14
+ resourceClass: EasBuildBillingResourceClass;
15
+ count: number;
16
+ costCents: number;
17
+ }
18
+ export interface BuildCountByPlatformAndSize {
19
+ platform: 'ios' | 'android';
20
+ resourceClass: 'medium' | 'large';
21
+ count: number;
22
+ }
23
+ export interface UsageDisplayData {
24
+ accountName: string;
25
+ subscriptionPlan: string;
26
+ billingPeriod: {
27
+ start: string;
28
+ end: string;
29
+ };
30
+ builds: {
31
+ total: UsageMetricDisplay;
32
+ ios?: UsageMetricDisplay;
33
+ android?: UsageMetricDisplay;
34
+ countsByPlatformAndSize: BuildCountByPlatformAndSize[];
35
+ overagesByWorkerSize: BuildOverageByWorkerSize[];
36
+ overageCostCents: number;
37
+ };
38
+ updates: {
39
+ mau: UsageMetricDisplay;
40
+ bandwidth: UsageMetricDisplay;
41
+ overageCostCents: number;
42
+ };
43
+ totalOverageCostCents: number;
44
+ estimatedBillCents: number;
45
+ recurringCents: number | null;
46
+ }
47
+ export declare function formatDate(dateString: string): string;
48
+ export declare function formatCurrency(cents: number): string;
49
+ export declare function formatNumber(value: number, decimals?: number): string;
50
+ export declare function calculateDaysRemaining(endDate: string): number;
51
+ export declare function calculateDaysElapsed(startDate: string): number;
52
+ export declare function calculateBillingPeriodDays(startDate: string, endDate: string): number;
53
+ export declare function extractUsageData(data: AccountFullUsageData): UsageDisplayData;
54
+ export declare function calculateBillingPeriodInfo(data: UsageDisplayData): {
55
+ daysRemaining: number;
56
+ daysElapsed: number;
57
+ totalDays: number;
58
+ };
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatDate = formatDate;
4
+ exports.formatCurrency = formatCurrency;
5
+ exports.formatNumber = formatNumber;
6
+ exports.calculateDaysRemaining = calculateDaysRemaining;
7
+ exports.calculateDaysElapsed = calculateDaysElapsed;
8
+ exports.calculateBillingPeriodDays = calculateBillingPeriodDays;
9
+ exports.extractUsageData = extractUsageData;
10
+ exports.calculateBillingPeriodInfo = calculateBillingPeriodInfo;
11
+ const generated_1 = require("../graphql/generated");
12
+ const checkForOverages_1 = require("../utils/usage/checkForOverages");
13
+ function formatDate(dateString) {
14
+ const date = new Date(dateString);
15
+ return date.toLocaleDateString('en-US', {
16
+ year: 'numeric',
17
+ month: 'short',
18
+ day: 'numeric',
19
+ });
20
+ }
21
+ function formatCurrency(cents) {
22
+ return `$${(cents / 100).toFixed(2)}`;
23
+ }
24
+ function formatNumber(value, decimals = 0) {
25
+ if (decimals === 0) {
26
+ return Math.round(value).toLocaleString();
27
+ }
28
+ return value.toLocaleString(undefined, {
29
+ minimumFractionDigits: decimals,
30
+ maximumFractionDigits: decimals,
31
+ });
32
+ }
33
+ function calculateDaysRemaining(endDate) {
34
+ const end = new Date(endDate);
35
+ const now = new Date();
36
+ const diffTime = end.getTime() - now.getTime();
37
+ return Math.max(0, Math.ceil(diffTime / (1000 * 60 * 60 * 24)));
38
+ }
39
+ function calculateDaysElapsed(startDate) {
40
+ const start = new Date(startDate);
41
+ const now = new Date();
42
+ const diffTime = now.getTime() - start.getTime();
43
+ return Math.max(1, Math.ceil(diffTime / (1000 * 60 * 60 * 24)));
44
+ }
45
+ function calculateBillingPeriodDays(startDate, endDate) {
46
+ const start = new Date(startDate);
47
+ const end = new Date(endDate);
48
+ const diffTime = end.getTime() - start.getTime();
49
+ return Math.max(1, Math.ceil(diffTime / (1000 * 60 * 60 * 24)));
50
+ }
51
+ function extractUsageData(data) {
52
+ const { name, subscription, billingPeriod, usageMetrics } = data;
53
+ const { EAS_BUILD, EAS_UPDATE, MEDIUM_ANDROID_BUILDS, LARGE_ANDROID_BUILDS, MEDIUM_IOS_BUILDS, LARGE_IOS_BUILDS, } = usageMetrics;
54
+ // Find build metrics
55
+ const buildMetric = EAS_BUILD.planMetrics.find(m => m.serviceMetric === generated_1.EasServiceMetric.Builds && m.metricType === generated_1.UsageMetricType.Build);
56
+ // Extract build counts by platform and worker size
57
+ const countsByPlatformAndSize = [];
58
+ const mediumAndroidCount = MEDIUM_ANDROID_BUILDS?.[0]?.value ?? 0;
59
+ const largeAndroidCount = LARGE_ANDROID_BUILDS?.[0]?.value ?? 0;
60
+ const mediumIosCount = MEDIUM_IOS_BUILDS?.[0]?.value ?? 0;
61
+ const largeIosCount = LARGE_IOS_BUILDS?.[0]?.value ?? 0;
62
+ if (mediumAndroidCount > 0) {
63
+ countsByPlatformAndSize.push({
64
+ platform: 'android',
65
+ resourceClass: 'medium',
66
+ count: mediumAndroidCount,
67
+ });
68
+ }
69
+ if (largeAndroidCount > 0) {
70
+ countsByPlatformAndSize.push({
71
+ platform: 'android',
72
+ resourceClass: 'large',
73
+ count: largeAndroidCount,
74
+ });
75
+ }
76
+ if (mediumIosCount > 0) {
77
+ countsByPlatformAndSize.push({
78
+ platform: 'ios',
79
+ resourceClass: 'medium',
80
+ count: mediumIosCount,
81
+ });
82
+ }
83
+ if (largeIosCount > 0) {
84
+ countsByPlatformAndSize.push({ platform: 'ios', resourceClass: 'large', count: largeIosCount });
85
+ }
86
+ // Extract build overages by worker size from overage metrics with metadata
87
+ const overagesByWorkerSize = [];
88
+ for (const overage of EAS_BUILD.overageMetrics) {
89
+ if (overage.serviceMetric === generated_1.EasServiceMetric.Builds &&
90
+ overage.metadata?.billingResourceClass &&
91
+ overage.metadata?.platform) {
92
+ overagesByWorkerSize.push({
93
+ platform: overage.metadata.platform,
94
+ resourceClass: overage.metadata.billingResourceClass,
95
+ count: overage.value,
96
+ costCents: overage.totalCost,
97
+ });
98
+ }
99
+ }
100
+ // Find update metrics
101
+ const mauMetric = EAS_UPDATE.planMetrics.find(m => m.serviceMetric === generated_1.EasServiceMetric.UniqueUpdaters && m.metricType === generated_1.UsageMetricType.Update);
102
+ const bandwidthMetric = EAS_UPDATE.planMetrics.find(m => m.serviceMetric === generated_1.EasServiceMetric.BandwidthUsage &&
103
+ m.metricType === generated_1.UsageMetricType.Bandwidth);
104
+ const mauOverage = EAS_UPDATE.overageMetrics.find(m => m.serviceMetric === generated_1.EasServiceMetric.UniqueUpdaters && m.metricType === generated_1.UsageMetricType.Update);
105
+ const bandwidthOverage = EAS_UPDATE.overageMetrics.find(m => m.serviceMetric === generated_1.EasServiceMetric.BandwidthUsage &&
106
+ m.metricType === generated_1.UsageMetricType.Bandwidth);
107
+ // Build metrics - plan values from planMetrics, overage from overageMetrics
108
+ const buildPlanValue = buildMetric?.value ?? 0;
109
+ const buildLimit = buildMetric?.limit ?? 0;
110
+ const iosBuildPlanValue = buildMetric?.platformBreakdown?.ios.value ?? 0;
111
+ const iosBuildLimit = buildMetric?.platformBreakdown?.ios.limit ?? 0;
112
+ const androidBuildPlanValue = buildMetric?.platformBreakdown?.android.value ?? 0;
113
+ const androidBuildLimit = buildMetric?.platformBreakdown?.android.limit ?? 0;
114
+ // Update metrics - plan values from planMetrics, overage from overageMetrics
115
+ const mauPlanValue = mauMetric?.value ?? 0;
116
+ const mauOverageValue = mauOverage?.value ?? 0;
117
+ const mauLimit = mauMetric?.limit ?? 0;
118
+ const bandwidthPlanValue = bandwidthMetric?.value ?? 0;
119
+ const bandwidthOverageValue = bandwidthOverage?.value ?? 0;
120
+ const bandwidthLimit = bandwidthMetric?.limit ?? 0;
121
+ const buildOverageCostCents = EAS_BUILD.totalCost;
122
+ const updateOverageCostCents = EAS_UPDATE.totalCost;
123
+ const totalOverageCostCents = buildOverageCostCents + updateOverageCostCents;
124
+ // Calculate total overage count for display
125
+ const totalOverageBuilds = overagesByWorkerSize.reduce((sum, o) => sum + o.count, 0);
126
+ return {
127
+ accountName: name,
128
+ subscriptionPlan: subscription?.name ?? 'Free',
129
+ billingPeriod: {
130
+ start: billingPeriod.start,
131
+ end: billingPeriod.end,
132
+ },
133
+ builds: {
134
+ total: {
135
+ name: 'Builds',
136
+ planValue: buildPlanValue,
137
+ limit: buildLimit,
138
+ percentUsed: (0, checkForOverages_1.calculatePercentUsed)(buildPlanValue, buildLimit),
139
+ overageValue: totalOverageBuilds,
140
+ overageCost: buildOverageCostCents,
141
+ unit: 'builds',
142
+ },
143
+ ios: buildMetric?.platformBreakdown
144
+ ? {
145
+ name: 'iOS Builds',
146
+ planValue: iosBuildPlanValue,
147
+ limit: iosBuildLimit,
148
+ percentUsed: (0, checkForOverages_1.calculatePercentUsed)(iosBuildPlanValue, iosBuildLimit),
149
+ overageValue: 0,
150
+ overageCost: 0,
151
+ unit: 'builds',
152
+ }
153
+ : undefined,
154
+ android: buildMetric?.platformBreakdown
155
+ ? {
156
+ name: 'Android Builds',
157
+ planValue: androidBuildPlanValue,
158
+ limit: androidBuildLimit,
159
+ percentUsed: (0, checkForOverages_1.calculatePercentUsed)(androidBuildPlanValue, androidBuildLimit),
160
+ overageValue: 0,
161
+ overageCost: 0,
162
+ unit: 'builds',
163
+ }
164
+ : undefined,
165
+ countsByPlatformAndSize,
166
+ overagesByWorkerSize,
167
+ overageCostCents: buildOverageCostCents,
168
+ },
169
+ updates: {
170
+ mau: {
171
+ name: 'Unique Updaters',
172
+ planValue: mauPlanValue,
173
+ limit: mauLimit,
174
+ percentUsed: (0, checkForOverages_1.calculatePercentUsed)(mauPlanValue, mauLimit),
175
+ overageValue: mauOverageValue,
176
+ overageCost: mauOverage?.totalCost ?? 0,
177
+ unit: 'users',
178
+ },
179
+ bandwidth: {
180
+ name: 'Bandwidth',
181
+ planValue: bandwidthPlanValue,
182
+ limit: bandwidthLimit,
183
+ percentUsed: (0, checkForOverages_1.calculatePercentUsed)(bandwidthPlanValue, bandwidthLimit),
184
+ overageValue: bandwidthOverageValue,
185
+ overageCost: bandwidthOverage?.totalCost ?? 0,
186
+ unit: 'bytes',
187
+ },
188
+ overageCostCents: updateOverageCostCents,
189
+ },
190
+ totalOverageCostCents,
191
+ estimatedBillCents: (subscription?.recurringCents ?? 0) + totalOverageCostCents,
192
+ recurringCents: subscription?.recurringCents ?? null,
193
+ };
194
+ }
195
+ function calculateBillingPeriodInfo(data) {
196
+ const daysElapsed = calculateDaysElapsed(data.billingPeriod.start);
197
+ const totalDays = calculateBillingPeriodDays(data.billingPeriod.start, data.billingPeriod.end);
198
+ const daysRemaining = calculateDaysRemaining(data.billingPeriod.end);
199
+ return {
200
+ daysRemaining,
201
+ daysElapsed,
202
+ totalDays,
203
+ };
204
+ }
@@ -4,6 +4,7 @@ export default class AccountLogin extends EasCommand {
4
4
  static aliases: string[];
5
5
  static flags: {
6
6
  sso: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ browser: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
8
  };
8
9
  static contextDefinition: {
9
10
  sessionManager: import("../../commandUtils/context/SessionManagementContextField").default;
@@ -17,13 +17,18 @@ class AccountLogin extends EasCommand_1.default {
17
17
  char: 's',
18
18
  default: false,
19
19
  }),
20
+ browser: core_1.Flags.boolean({
21
+ description: 'Login with your browser',
22
+ char: 'b',
23
+ default: false,
24
+ }),
20
25
  };
21
26
  static contextDefinition = {
22
27
  ...this.ContextOptions.MaybeLoggedIn,
23
28
  ...this.ContextOptions.SessionManagment,
24
29
  };
25
30
  async runAsync() {
26
- const { flags: { sso }, } = await this.parse(AccountLogin);
31
+ const { flags: { sso, browser }, } = await this.parse(AccountLogin);
27
32
  const { sessionManager, maybeLoggedIn: { actor }, } = await this.getContextAsync(AccountLogin, { nonInteractive: false });
28
33
  if (sessionManager.getAccessToken()) {
29
34
  throw new Error('EXPO_TOKEN is set in your environment, and is being used for all EAS authentication. To use username/password authentication, unset EXPO_TOKEN in your environment and re-run the command.');
@@ -37,7 +42,7 @@ class AccountLogin extends EasCommand_1.default {
37
42
  core_1.Errors.error('Aborted', { exit: 1 });
38
43
  }
39
44
  }
40
- await sessionManager.showLoginPromptAsync({ sso });
45
+ await sessionManager.showLoginPromptAsync({ sso, browser });
41
46
  log_1.default.log('Logged in');
42
47
  }
43
48
  }
@@ -0,0 +1,16 @@
1
+ import EasCommand from '../../commandUtils/EasCommand';
2
+ export default class AccountUsage extends EasCommand {
3
+ static description: string;
4
+ static args: {
5
+ name: string;
6
+ description: string;
7
+ }[];
8
+ static flags: {
9
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ };
12
+ static contextDefinition: {
13
+ loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
14
+ };
15
+ runAsync(): Promise<void>;
16
+ }