eas-cli 16.30.0 → 16.32.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.
@@ -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;
@@ -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
+ }
@@ -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
+ }
@@ -0,0 +1,326 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
5
+ const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
6
+ const flags_1 = require("../../commandUtils/flags");
7
+ const usageUtils_1 = require("../../commandUtils/usageUtils");
8
+ const generated_1 = require("../../graphql/generated");
9
+ const AccountQuery_1 = require("../../graphql/queries/AccountQuery");
10
+ const log_1 = tslib_1.__importStar(require("../../log"));
11
+ const ora_1 = require("../../ora");
12
+ const prompts_1 = require("../../prompts");
13
+ const json_1 = require("../../utils/json");
14
+ const checkForOverages_1 = require("../../utils/usage/checkForOverages");
15
+ const MiB = 1024 * 1024;
16
+ const GiB = 1024 * MiB;
17
+ const TiB = 1024 * GiB;
18
+ function formatBytes(bytes) {
19
+ if (bytes >= TiB) {
20
+ return { value: bytes / TiB, unit: 'TiB' };
21
+ }
22
+ else if (bytes >= GiB) {
23
+ return { value: bytes / GiB, unit: 'GiB' };
24
+ }
25
+ else {
26
+ return { value: bytes / MiB, unit: 'MiB' };
27
+ }
28
+ }
29
+ function formatBytesDisplay(bytes) {
30
+ const { value, unit } = formatBytes(bytes);
31
+ return `${(0, usageUtils_1.formatNumber)(value, value < 10 ? 2 : value < 100 ? 1 : 0)} ${unit}`;
32
+ }
33
+ function displayBandwidthMetric(metric, indent = ' ') {
34
+ const progressBar = (0, checkForOverages_1.createProgressBar)(metric.percentUsed, 20);
35
+ const percentStr = `${metric.percentUsed}%`;
36
+ const planUsageStr = `${formatBytesDisplay(metric.planValue)}/${formatBytesDisplay(metric.limit)}`;
37
+ let color = chalk_1.default.green;
38
+ if (metric.percentUsed >= 100) {
39
+ color = chalk_1.default.red;
40
+ }
41
+ else if (metric.percentUsed >= 85) {
42
+ color = chalk_1.default.yellow;
43
+ }
44
+ log_1.default.log(`${indent}${metric.name} (plan): ${color(planUsageStr)}`);
45
+ log_1.default.log(`${indent}${progressBar} ${color(percentStr)}`);
46
+ if (metric.overageValue > 0) {
47
+ log_1.default.log(`${indent}${metric.name} (overage): ${chalk_1.default.red(formatBytesDisplay(metric.overageValue))} (${(0, usageUtils_1.formatCurrency)(metric.overageCost)})`);
48
+ }
49
+ }
50
+ function displayMetric(metric, indent = ' ') {
51
+ const progressBar = (0, checkForOverages_1.createProgressBar)(metric.percentUsed, 20);
52
+ const percentStr = `${metric.percentUsed}%`;
53
+ const planUsageStr = `${(0, usageUtils_1.formatNumber)(metric.planValue)}/${(0, usageUtils_1.formatNumber)(metric.limit)} ${metric.unit ?? ''}`;
54
+ let color = chalk_1.default.green;
55
+ if (metric.percentUsed >= 100) {
56
+ color = chalk_1.default.red;
57
+ }
58
+ else if (metric.percentUsed >= 85) {
59
+ color = chalk_1.default.yellow;
60
+ }
61
+ log_1.default.log(`${indent}${metric.name} (plan): ${color(planUsageStr)}`);
62
+ log_1.default.log(`${indent}${progressBar} ${color(percentStr)}`);
63
+ if (metric.overageValue > 0) {
64
+ log_1.default.log(`${indent}${metric.name} (overage): ${chalk_1.default.red(`${(0, usageUtils_1.formatNumber)(metric.overageValue)} ${metric.unit ?? ''}`)} (${(0, usageUtils_1.formatCurrency)(metric.overageCost)})`);
65
+ }
66
+ }
67
+ function billingUrl(accountName) {
68
+ return `https://expo.dev/accounts/${accountName}/settings/billing`;
69
+ }
70
+ function displayUsage(data, usageData) {
71
+ const subscription = usageData.subscription;
72
+ log_1.default.newLine();
73
+ log_1.default.log(chalk_1.default.bold(`Account: ${data.accountName}`));
74
+ log_1.default.log(`Plan: ${data.subscriptionPlan}`);
75
+ if (subscription?.concurrencies) {
76
+ log_1.default.log(`Concurrencies: ${subscription.concurrencies.total} total (iOS: ${subscription.concurrencies.ios}, Android: ${subscription.concurrencies.android})`);
77
+ }
78
+ log_1.default.log(`Billing Period: ${(0, usageUtils_1.formatDate)(data.billingPeriod.start)} - ${(0, usageUtils_1.formatDate)(data.billingPeriod.end)}`);
79
+ const periodInfo = (0, usageUtils_1.calculateBillingPeriodInfo)(data);
80
+ log_1.default.log(`Days: ${periodInfo.daysElapsed} elapsed / ${periodInfo.totalDays} total (${periodInfo.daysRemaining} remaining)`);
81
+ log_1.default.newLine();
82
+ log_1.default.log(chalk_1.default.bold.underline('EAS Build'));
83
+ displayMetric(data.builds.total);
84
+ if (data.builds.ios) {
85
+ displayMetric(data.builds.ios, ' ');
86
+ }
87
+ if (data.builds.android) {
88
+ displayMetric(data.builds.android, ' ');
89
+ }
90
+ // Show build counts by platform and worker size
91
+ if (data.builds.countsByPlatformAndSize.length > 0) {
92
+ log_1.default.log(' Breakdown by platform/worker:');
93
+ for (const item of data.builds.countsByPlatformAndSize) {
94
+ const platformName = item.platform === 'ios' ? 'iOS' : 'Android';
95
+ log_1.default.log(` ${platformName} ${item.resourceClass}: ${(0, usageUtils_1.formatNumber)(item.count)} builds`);
96
+ }
97
+ }
98
+ log_1.default.newLine();
99
+ log_1.default.log(chalk_1.default.bold.underline('EAS Update'));
100
+ displayMetric(data.updates.mau);
101
+ displayBandwidthMetric(data.updates.bandwidth);
102
+ log_1.default.newLine();
103
+ log_1.default.log(chalk_1.default.bold.underline('Billing'));
104
+ const upcomingInvoice = subscription?.upcomingInvoice;
105
+ // Show subscription addons
106
+ if (subscription?.addons && subscription.addons.length > 0) {
107
+ log_1.default.log(' Addons:');
108
+ for (const addon of subscription.addons) {
109
+ log_1.default.log(` ${addon.name}: ${addon.quantity}`);
110
+ }
111
+ }
112
+ // Show upcoming invoice line items
113
+ if (upcomingInvoice?.lineItems && upcomingInvoice.lineItems.length > 0) {
114
+ log_1.default.newLine();
115
+ log_1.default.log(' Upcoming invoice:');
116
+ for (const lineItem of upcomingInvoice.lineItems) {
117
+ const amount = lineItem.amount;
118
+ const amountStr = amount < 0 ? chalk_1.default.green((0, usageUtils_1.formatCurrency)(amount)) : (0, usageUtils_1.formatCurrency)(amount);
119
+ log_1.default.log(` ${lineItem.description}: ${amountStr}`);
120
+ }
121
+ log_1.default.newLine();
122
+ log_1.default.log(` Invoice total: ${chalk_1.default.bold((0, usageUtils_1.formatCurrency)(upcomingInvoice.total))}`);
123
+ }
124
+ else {
125
+ // Fallback to the calculated estimate if no upcoming invoice
126
+ if (data.recurringCents !== null && data.recurringCents > 0) {
127
+ log_1.default.log(` Base subscription: ${(0, usageUtils_1.formatCurrency)(data.recurringCents)}`);
128
+ }
129
+ if (data.totalOverageCostCents > 0) {
130
+ log_1.default.log(` Current overages: ${chalk_1.default.yellow((0, usageUtils_1.formatCurrency)(data.totalOverageCostCents))}`);
131
+ if (data.builds.overageCostCents > 0) {
132
+ log_1.default.log(` Build overages: ${(0, usageUtils_1.formatCurrency)(data.builds.overageCostCents)}`);
133
+ // Show breakdown by worker size
134
+ for (const overage of data.builds.overagesByWorkerSize) {
135
+ const platformName = overage.platform === generated_1.AppPlatform.Ios ? 'iOS' : 'Android';
136
+ const sizeName = overage.resourceClass === generated_1.EasBuildBillingResourceClass.Large ? 'large' : 'medium';
137
+ log_1.default.log(` ${platformName} ${sizeName}: ${(0, usageUtils_1.formatNumber)(overage.count)} builds (${(0, usageUtils_1.formatCurrency)(overage.costCents)})`);
138
+ }
139
+ }
140
+ if (data.updates.overageCostCents > 0) {
141
+ log_1.default.log(` Update overages: ${(0, usageUtils_1.formatCurrency)(data.updates.overageCostCents)}`);
142
+ }
143
+ }
144
+ log_1.default.log(` Estimated bill: ${chalk_1.default.bold((0, usageUtils_1.formatCurrency)(data.estimatedBillCents))}`);
145
+ }
146
+ log_1.default.newLine();
147
+ log_1.default.log(chalk_1.default.dim(`View detailed billing: ${(0, log_1.link)(billingUrl(data.accountName))}`));
148
+ }
149
+ class AccountUsage extends EasCommand_1.default {
150
+ static description = 'view account usage and billing for the current cycle';
151
+ static args = [
152
+ {
153
+ name: 'ACCOUNT_NAME',
154
+ description: 'Account name to view usage for. If not provided, the account will be selected interactively (or defaults to the only account if there is just one)',
155
+ },
156
+ ];
157
+ static flags = {
158
+ ...flags_1.EasJsonOnlyFlag,
159
+ ...flags_1.EASNonInteractiveFlag,
160
+ };
161
+ static contextDefinition = {
162
+ ...this.ContextOptions.LoggedIn,
163
+ };
164
+ async runAsync() {
165
+ const { args: { ACCOUNT_NAME: accountName }, flags: { json: jsonFlag, 'non-interactive': nonInteractive }, } = await this.parse(AccountUsage);
166
+ // Enable JSON output if either --json or --non-interactive is passed
167
+ const json = jsonFlag || nonInteractive;
168
+ if (json) {
169
+ (0, json_1.enableJsonOutput)();
170
+ }
171
+ const { loggedIn: { graphqlClient, actor }, } = await this.getContextAsync(AccountUsage, {
172
+ nonInteractive,
173
+ });
174
+ // Find the target account
175
+ const defaultAccount = actor.accounts[0];
176
+ let targetAccount;
177
+ const availableAccounts = actor.accounts.map(a => a.name).join(', ');
178
+ if (accountName) {
179
+ // First check if it's one of the user's accounts
180
+ const found = actor.accounts.find(a => a.name === accountName);
181
+ if (found) {
182
+ targetAccount = found;
183
+ }
184
+ else {
185
+ // Try to look up the account by name (user may have access via organization)
186
+ try {
187
+ const account = await AccountQuery_1.AccountQuery.getByNameAsync(graphqlClient, accountName);
188
+ if (!account) {
189
+ throw new Error(`Account "${accountName}" not found. Available accounts: ${availableAccounts}`);
190
+ }
191
+ targetAccount = account;
192
+ }
193
+ catch {
194
+ throw new Error(`Account "${accountName}" not found or you don't have access. Available accounts: ${availableAccounts}`);
195
+ }
196
+ }
197
+ }
198
+ else if (nonInteractive) {
199
+ throw new Error('ACCOUNT_NAME argument must be provided when running in `--non-interactive` mode.');
200
+ }
201
+ else if (actor.accounts.length === 1) {
202
+ // Only one account, use it directly
203
+ targetAccount = defaultAccount;
204
+ }
205
+ else {
206
+ // Prompt user to select an account
207
+ targetAccount = await (0, prompts_1.selectAsync)('Select account to view usage for:', actor.accounts.map(account => ({
208
+ title: account.name,
209
+ value: account,
210
+ })), { initial: defaultAccount });
211
+ }
212
+ const spinner = (0, ora_1.ora)(`Fetching usage data for account ${targetAccount.name}`).start();
213
+ try {
214
+ const currentDate = new Date();
215
+ const { start, end } = await AccountQuery_1.AccountQuery.getBillingPeriodAsync(graphqlClient, targetAccount.id, currentDate);
216
+ const usageData = await AccountQuery_1.AccountQuery.getFullUsageAsync(graphqlClient, targetAccount.id, currentDate, start, end);
217
+ log_1.default.debug(JSON.stringify(usageData, null, 2));
218
+ spinner.succeed(`Usage data loaded for ${targetAccount.name}`);
219
+ const displayData = (0, usageUtils_1.extractUsageData)(usageData);
220
+ const periodInfo = (0, usageUtils_1.calculateBillingPeriodInfo)(displayData);
221
+ if (json) {
222
+ const subscription = usageData.subscription;
223
+ (0, json_1.printJsonOnlyOutput)({
224
+ account: {
225
+ name: displayData.accountName,
226
+ plan: displayData.subscriptionPlan,
227
+ concurrencies: subscription?.concurrencies ?? null,
228
+ billingPeriod: {
229
+ start: displayData.billingPeriod.start,
230
+ end: displayData.billingPeriod.end,
231
+ daysElapsed: periodInfo.daysElapsed,
232
+ daysRemaining: periodInfo.daysRemaining,
233
+ totalDays: periodInfo.totalDays,
234
+ },
235
+ },
236
+ builds: {
237
+ plan: {
238
+ used: displayData.builds.total.planValue,
239
+ limit: displayData.builds.total.limit,
240
+ percentUsed: displayData.builds.total.percentUsed,
241
+ },
242
+ overage: {
243
+ count: displayData.builds.total.overageValue,
244
+ costCents: displayData.builds.total.overageCost,
245
+ byWorkerSize: displayData.builds.overagesByWorkerSize.map(o => ({
246
+ platform: o.platform.toLowerCase(),
247
+ resourceClass: o.resourceClass.toLowerCase(),
248
+ count: o.count,
249
+ costCents: o.costCents,
250
+ })),
251
+ },
252
+ byPlatformAndSize: displayData.builds.countsByPlatformAndSize.map(item => ({
253
+ platform: item.platform,
254
+ resourceClass: item.resourceClass,
255
+ count: item.count,
256
+ })),
257
+ ios: displayData.builds.ios
258
+ ? {
259
+ plan: {
260
+ used: displayData.builds.ios.planValue,
261
+ limit: displayData.builds.ios.limit,
262
+ percentUsed: displayData.builds.ios.percentUsed,
263
+ },
264
+ }
265
+ : null,
266
+ android: displayData.builds.android
267
+ ? {
268
+ plan: {
269
+ used: displayData.builds.android.planValue,
270
+ limit: displayData.builds.android.limit,
271
+ percentUsed: displayData.builds.android.percentUsed,
272
+ },
273
+ }
274
+ : null,
275
+ },
276
+ updates: {
277
+ uniqueUpdaters: {
278
+ plan: {
279
+ used: displayData.updates.mau.planValue,
280
+ limit: displayData.updates.mau.limit,
281
+ percentUsed: displayData.updates.mau.percentUsed,
282
+ },
283
+ overage: {
284
+ count: displayData.updates.mau.overageValue,
285
+ costCents: displayData.updates.mau.overageCost,
286
+ },
287
+ },
288
+ bandwidth: {
289
+ plan: {
290
+ usedBytes: displayData.updates.bandwidth.planValue,
291
+ usedFormatted: formatBytesDisplay(displayData.updates.bandwidth.planValue),
292
+ limitBytes: displayData.updates.bandwidth.limit,
293
+ limitFormatted: formatBytesDisplay(displayData.updates.bandwidth.limit),
294
+ percentUsed: displayData.updates.bandwidth.percentUsed,
295
+ },
296
+ overage: {
297
+ usedBytes: displayData.updates.bandwidth.overageValue,
298
+ usedFormatted: formatBytesDisplay(displayData.updates.bandwidth.overageValue),
299
+ costCents: displayData.updates.bandwidth.overageCost,
300
+ },
301
+ },
302
+ overageCostCents: displayData.updates.overageCostCents,
303
+ },
304
+ billing: {
305
+ addons: subscription?.addons ?? [],
306
+ upcomingInvoice: subscription?.upcomingInvoice ?? null,
307
+ estimated: {
308
+ recurringCents: displayData.recurringCents,
309
+ overageCents: displayData.totalOverageCostCents,
310
+ totalCents: displayData.estimatedBillCents,
311
+ },
312
+ },
313
+ billingUrl: billingUrl(displayData.accountName),
314
+ });
315
+ }
316
+ else {
317
+ displayUsage(displayData, usageData);
318
+ }
319
+ }
320
+ catch (error) {
321
+ spinner.fail('Failed to fetch usage data');
322
+ throw error;
323
+ }
324
+ }
325
+ }
326
+ exports.default = AccountUsage;
@@ -0,0 +1,12 @@
1
+ import EasCommand from '../../commandUtils/EasCommand';
2
+ export default class SubmitUploadToAsc extends EasCommand {
3
+ static hidden: boolean;
4
+ static flags: {
5
+ path: import("@oclif/core/lib/interfaces").OptionFlag<string>;
6
+ key: import("@oclif/core/lib/interfaces").OptionFlag<string>;
7
+ 'app-id': import("@oclif/core/lib/interfaces").OptionFlag<string>;
8
+ 'bundle-version': import("@oclif/core/lib/interfaces").OptionFlag<string>;
9
+ 'bundle-short-version': import("@oclif/core/lib/interfaces").OptionFlag<string>;
10
+ };
11
+ runAsync(): Promise<void>;
12
+ }