eas-cli 20.2.0 → 20.4.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 (47) hide show
  1. package/README.md +225 -110
  2. package/build/commandUtils/new/templates/AGENTS.md +25 -146
  3. package/build/commandUtils/new/templates/CLAUDE.md +1 -9
  4. package/build/commandUtils/posthog.d.ts +4 -0
  5. package/build/commandUtils/posthog.js +23 -0
  6. package/build/commands/account/audit.d.ts +17 -0
  7. package/build/commands/account/audit.js +112 -0
  8. package/build/commands/integrations/posthog/connect.d.ts +27 -0
  9. package/build/commands/integrations/posthog/connect.js +432 -0
  10. package/build/commands/integrations/posthog/dashboard.d.ts +13 -0
  11. package/build/commands/integrations/posthog/dashboard.js +66 -0
  12. package/build/commands/integrations/posthog/disconnect.d.ts +14 -0
  13. package/build/commands/integrations/posthog/disconnect.js +80 -0
  14. package/build/commands/observe/session.d.ts +18 -0
  15. package/build/commands/observe/session.js +65 -0
  16. package/build/commands/update/view.d.ts +7 -0
  17. package/build/commands/update/view.js +30 -3
  18. package/build/graphql/generated.d.ts +468 -2
  19. package/build/graphql/generated.js +28 -4
  20. package/build/graphql/mutations/PostHogMutation.d.ts +8 -0
  21. package/build/graphql/mutations/PostHogMutation.js +55 -0
  22. package/build/graphql/queries/AuditLogQuery.d.ts +6 -0
  23. package/build/graphql/queries/AuditLogQuery.js +57 -0
  24. package/build/graphql/queries/DeviceRunSessionQuery.js +1 -0
  25. package/build/graphql/queries/PostHogQuery.d.ts +6 -0
  26. package/build/graphql/queries/PostHogQuery.js +49 -0
  27. package/build/graphql/types/AuditLog.d.ts +1 -0
  28. package/build/graphql/types/AuditLog.js +18 -0
  29. package/build/graphql/types/Observe.js +1 -0
  30. package/build/graphql/types/PostHogConnection.d.ts +7 -0
  31. package/build/graphql/types/PostHogConnection.js +30 -0
  32. package/build/observe/fetchCustomEvents.d.ts +2 -2
  33. package/build/observe/fetchCustomEvents.js +2 -2
  34. package/build/observe/fetchEvents.d.ts +4 -3
  35. package/build/observe/fetchEvents.js +4 -3
  36. package/build/observe/fetchSessions.d.ts +51 -0
  37. package/build/observe/fetchSessions.js +86 -0
  38. package/build/observe/formatEvents.d.ts +1 -0
  39. package/build/observe/formatEvents.js +1 -0
  40. package/build/observe/formatSessions.d.ts +15 -0
  41. package/build/observe/formatSessions.js +100 -0
  42. package/build/simulator/utils.js +28 -5
  43. package/build/update/getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync.js +23 -26
  44. package/build/user/SessionManager.d.ts +1 -22
  45. package/build/user/SessionManager.js +7 -89
  46. package/oclif.manifest.json +1583 -1108
  47. package/package.json +7 -3
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildObserveSessionEventsTable = buildObserveSessionEventsTable;
4
+ exports.buildObserveSessionEventsJson = buildObserveSessionEventsJson;
5
+ const tslib_1 = require("tslib");
6
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
7
+ const formatUtils_1 = require("./formatUtils");
8
+ const metricNames_1 = require("./metricNames");
9
+ const renderTextTable_1 = tslib_1.__importDefault(require("../utils/renderTextTable"));
10
+ function formatEntryName(entry) {
11
+ if (entry.source === 'metric' && entry.metricName) {
12
+ const name = (0, metricNames_1.getMetricDisplayName)(entry.metricName);
13
+ return entry.routeName ? `${name} · ${entry.routeName}` : name;
14
+ }
15
+ return entry.eventName ?? '-';
16
+ }
17
+ function formatMetricEntryValue(entry) {
18
+ if (typeof entry.metricValue === 'number') {
19
+ return `${entry.metricValue.toFixed(2)}s`;
20
+ }
21
+ return '-';
22
+ }
23
+ function formatEntrySeverity(entry) {
24
+ if (entry.source !== 'log') {
25
+ return '-';
26
+ }
27
+ if (entry.severityText) {
28
+ return entry.severityText;
29
+ }
30
+ if (entry.severityNumber != null) {
31
+ return String(entry.severityNumber);
32
+ }
33
+ return '-';
34
+ }
35
+ function primitivePropertyLines(entry) {
36
+ if (entry.source !== 'log' || !entry.properties) {
37
+ return [];
38
+ }
39
+ return entry.properties
40
+ .filter(p => p.type === 'STRING' || p.type === 'NUMBER' || p.type === 'BOOLEAN')
41
+ .map(p => `${p.key}=${p.value}`);
42
+ }
43
+ function formatOffsetSeconds(startIso, currentIso) {
44
+ const offsetMs = new Date(currentIso).getTime() - new Date(startIso).getTime();
45
+ return `${(offsetMs / 1000).toFixed(2)}s`;
46
+ }
47
+ function buildObserveSessionEventsTable(entries, options) {
48
+ const lines = [];
49
+ if (options?.metadata) {
50
+ const { metadata } = options;
51
+ lines.push(`App version: ${metadata.appVersion} (${metadata.appBuildNumber})`, `Device: ${metadata.deviceModel} · ${metadata.deviceOs} ${metadata.deviceOsVersion}`, `First seen: ${(0, formatUtils_1.formatLogTimestamp)(metadata.firstSeenAt)}`, `Last seen: ${(0, formatUtils_1.formatLogTimestamp)(metadata.lastSeenAt)}`, '');
52
+ }
53
+ if (entries.length === 0) {
54
+ lines.push(chalk_1.default.yellow('No events found for this session.'));
55
+ return lines.join('\n');
56
+ }
57
+ const startIso = entries[0].timestamp;
58
+ const headers = ['Offset', 'Type', 'Name', 'Value', 'Properties', 'Severity'];
59
+ const rows = [];
60
+ for (const entry of entries) {
61
+ const offset = formatOffsetSeconds(startIso, entry.timestamp);
62
+ const type = entry.source === 'metric' ? 'metric' : 'log';
63
+ const name = formatEntryName(entry);
64
+ const severity = formatEntrySeverity(entry);
65
+ if (entry.source === 'metric') {
66
+ rows.push([offset, type, name, formatMetricEntryValue(entry), '-', severity]);
67
+ continue;
68
+ }
69
+ const propLines = primitivePropertyLines(entry);
70
+ if (propLines.length === 0) {
71
+ rows.push([offset, type, name, '-', '-', severity]);
72
+ continue;
73
+ }
74
+ rows.push([offset, type, name, '-', propLines[0], severity]);
75
+ for (let i = 1; i < propLines.length; i++) {
76
+ rows.push(['', '', '', '', propLines[i], '']);
77
+ }
78
+ }
79
+ lines.push((0, renderTextTable_1.default)(headers, rows));
80
+ if (options?.hasMoreMetricEvents || options?.hasMoreLogEvents) {
81
+ const sources = [];
82
+ if (options.hasMoreMetricEvents) {
83
+ sources.push('metric events');
84
+ }
85
+ if (options.hasMoreLogEvents) {
86
+ sources.push('log events');
87
+ }
88
+ lines.push('', chalk_1.default.yellow(`More ${sources.join(' and ')} are available for this session; only the first 100 of each are shown.`));
89
+ }
90
+ return lines.join('\n');
91
+ }
92
+ function buildObserveSessionEventsJson(entries, sessionId, metadata, hasMoreMetricEvents, hasMoreLogEvents) {
93
+ return {
94
+ sessionId,
95
+ metadata,
96
+ entries,
97
+ hasMoreMetricEvents,
98
+ hasMoreLogEvents,
99
+ };
100
+ }
@@ -24,6 +24,10 @@ function getRemoteSessionEnvironmentVariables(remoteConfig) {
24
24
  AGENT_DEVICE_DAEMON_AUTH_TOKEN: remoteConfig.agentDeviceRemoteSessionToken,
25
25
  };
26
26
  case 'ArgentRunSessionRemoteConfig':
27
+ return {
28
+ ARGENT_TOOLS_URL: remoteConfig.toolsUrl,
29
+ ...(remoteConfig.toolsAuthToken ? { ARGENT_AUTH_TOKEN: remoteConfig.toolsAuthToken } : {}),
30
+ };
27
31
  case 'ServeSimRunSessionRemoteConfig':
28
32
  return {};
29
33
  }
@@ -49,11 +53,30 @@ function formatRemoteSessionInstructions(remoteConfig, configType) {
49
53
  return lines.join('\n');
50
54
  }
51
55
  case 'ArgentRunSessionRemoteConfig': {
52
- const lines = [
53
- '🔑 Open the following URL to access the Argent tools for this session:',
54
- '',
55
- remoteConfig.toolsUrl,
56
- ];
56
+ const environmentVariables = getRemoteSessionEnvironmentVariables(remoteConfig);
57
+ const lines = configType === 'dotenv'
58
+ ? [
59
+ '🔑 Run the following to link your local Argent client to this simulator session:',
60
+ '',
61
+ [
62
+ 'argent',
63
+ 'link',
64
+ `'${remoteConfig.toolsUrl}'`,
65
+ remoteConfig.toolsAuthToken
66
+ ? `--token '${remoteConfig.toolsAuthToken}'`
67
+ : undefined,
68
+ '--yes',
69
+ ]
70
+ .filter(Boolean)
71
+ .join(' '),
72
+ '',
73
+ 'Restart your editor after linking so its Argent MCP process uses the remote session.',
74
+ ]
75
+ : [
76
+ '🔑 Run the following in your shell to attach Argent to this simulator session:',
77
+ '',
78
+ ...Object.entries(environmentVariables).map(([key, value]) => `export ${key}='${value}'`),
79
+ ];
57
80
  if (remoteConfig.webPreviewUrl) {
58
81
  lines.push('', '🌐 Open the following URL in your browser to preview the simulator:', '', remoteConfig.webPreviewUrl);
59
82
  }
@@ -6,40 +6,37 @@ const errors_1 = require("../channel/errors");
6
6
  const queries_2 = require("../channel/queries");
7
7
  const ChannelQuery_1 = require("../graphql/queries/ChannelQuery");
8
8
  async function getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync(graphqlClient, projectId, channelName) {
9
- let branchInfo;
9
+ let channel;
10
10
  try {
11
- const channel = await ChannelQuery_1.ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
11
+ channel = await ChannelQuery_1.ChannelQuery.viewUpdateChannelAsync(graphqlClient, {
12
12
  appId: projectId,
13
13
  channelName,
14
14
  });
15
- if (channel.updateBranches.length === 1) {
16
- const branch = channel.updateBranches[0];
17
- branchInfo = { branchId: branch.id, branchName: branch.name };
18
- }
19
- else if (channel.updateBranches.length === 0) {
20
- throw new Error("Channel has no branches associated with it. Run 'eas channel:edit' to map a branch");
21
- }
22
- else {
23
- throw new Error(`Channel has multiple branches associated with it. Instead, use '--branch' instead of '--channel'`);
24
- }
25
15
  }
26
16
  catch (error) {
27
17
  if (!(error instanceof errors_1.ChannelNotFoundError)) {
28
18
  throw error;
29
19
  }
30
- const { branch } = await (0, queries_1.ensureBranchExistsAsync)(graphqlClient, {
31
- appId: projectId,
32
- branchName: channelName,
33
- });
34
- const { updateChannel: { createUpdateChannelForApp: newChannel }, } = await (0, queries_2.createChannelOnAppAsync)(graphqlClient, {
35
- appId: projectId,
36
- channelName,
37
- branchId: branch.id,
38
- });
39
- if (!newChannel) {
40
- throw new Error(`Could not create channel with name ${channelName} on project with id ${projectId}`);
41
- }
42
- branchInfo = { branchId: branch.id, branchName: channelName };
20
+ return await createAndLinkBranchToChannelAsync(graphqlClient, projectId, channelName);
43
21
  }
44
- return branchInfo;
22
+ if (channel.updateBranches.length === 1) {
23
+ const branch = channel.updateBranches[0];
24
+ return { branchId: branch.id, branchName: branch.name };
25
+ }
26
+ if (channel.updateBranches.length === 0) {
27
+ return await createAndLinkBranchToChannelAsync(graphqlClient, projectId, channelName);
28
+ }
29
+ throw new Error(`Channel has multiple branches associated with it. Instead, use '--branch' instead of '--channel'`);
30
+ }
31
+ async function createAndLinkBranchToChannelAsync(graphqlClient, projectId, channelName) {
32
+ const { branch } = await (0, queries_1.ensureBranchExistsAsync)(graphqlClient, {
33
+ appId: projectId,
34
+ branchName: channelName,
35
+ });
36
+ await (0, queries_2.createChannelOnAppAsync)(graphqlClient, {
37
+ appId: projectId,
38
+ channelName,
39
+ branchId: branch.id,
40
+ });
41
+ return { branchId: branch.id, branchName: channelName };
45
42
  }
@@ -1,9 +1,5 @@
1
1
  import { AnalyticsWithOrchestration } from '../analytics/AnalyticsManager';
2
2
  import { CurrentUserQuery } from '../graphql/generated';
3
- export declare enum UserSecondFactorDeviceMethod {
4
- AUTHENTICATOR = "authenticator",
5
- SMS = "sms"
6
- }
7
3
  export type LoggedInAuthenticationInfo = {
8
4
  accessToken: string;
9
5
  sessionSecret: null;
@@ -52,24 +48,7 @@ export default class SessionManager {
52
48
  */
53
49
  private promptForOTPAsync;
54
50
  /**
55
- * Prompt for user to choose a backup OTP method. If selected method is SMS, a request
56
- * for a new OTP will be sent to that method. Then, prompt for the OTP, and retry the user login.
57
- */
58
- private promptForBackupOTPAsync;
59
- /**
60
- * Handle the special case error indicating that a second-factor is required for
61
- * authentication.
62
- *
63
- * There are three cases we need to handle:
64
- * 1. User's primary second-factor device was SMS, OTP was automatically sent by the server to that
65
- * device already. In this case we should just prompt for the SMS OTP (or backup code), which the
66
- * user should be receiving shortly. We should give the user a way to cancel and the prompt and move
67
- * to case 3 below.
68
- * 2. User's primary second-factor device is authenticator. In this case we should prompt for authenticator
69
- * OTP (or backup code) and also give the user a way to cancel and move to case 3 below.
70
- * 3. User doesn't have a primary device or doesn't have access to their primary device. In this case
71
- * we should show a picker of the SMS devices that they can have an OTP code sent to, and when
72
- * the user picks one we show a prompt() for the sent OTP.
51
+ * Handle the special case error indicating that a second-factor is required for authentication.
73
52
  */
74
53
  private retryUsernamePasswordAuthWithOTPAsync;
75
54
  }
@@ -1,10 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UserSecondFactorDeviceMethod = void 0;
4
3
  const tslib_1 = require("tslib");
5
4
  const json_file_1 = tslib_1.__importDefault(require("@expo/json-file"));
6
5
  const core_1 = require("@oclif/core");
7
- const assert_1 = tslib_1.__importDefault(require("assert"));
8
6
  const chalk_1 = tslib_1.__importDefault(require("chalk"));
9
7
  const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
10
8
  const fetchSessionSecretAndUser_1 = require("./fetchSessionSecretAndUser");
@@ -16,11 +14,6 @@ const UserQuery_1 = require("../graphql/queries/UserQuery");
16
14
  const log_1 = tslib_1.__importStar(require("../log"));
17
15
  const prompts_1 = require("../prompts");
18
16
  const paths_1 = require("../utils/paths");
19
- var UserSecondFactorDeviceMethod;
20
- (function (UserSecondFactorDeviceMethod) {
21
- UserSecondFactorDeviceMethod["AUTHENTICATOR"] = "authenticator";
22
- UserSecondFactorDeviceMethod["SMS"] = "sms";
23
- })(UserSecondFactorDeviceMethod || (exports.UserSecondFactorDeviceMethod = UserSecondFactorDeviceMethod = {}));
24
17
  class SessionManager {
25
18
  analytics;
26
19
  currentActor;
@@ -145,7 +138,7 @@ class SessionManager {
145
138
  }
146
139
  catch (e) {
147
140
  if (e instanceof ApiV2Error_1.ApiV2Error && e.expoApiV2ErrorCode === 'ONE_TIME_PASSWORD_REQUIRED') {
148
- await this.retryUsernamePasswordAuthWithOTPAsync(username, password, e.expoApiV2ErrorMetadata);
141
+ await this.retryUsernamePasswordAuthWithOTPAsync(username, password);
149
142
  }
150
143
  else {
151
144
  throw e;
@@ -173,10 +166,8 @@ class SessionManager {
173
166
  /**
174
167
  * Prompt for an OTP with the option to cancel the question by answering empty (pressing return key).
175
168
  */
176
- async promptForOTPAsync(cancelBehavior) {
177
- const enterMessage = cancelBehavior === 'cancel'
178
- ? `press ${chalk_1.default.bold('Enter')} to cancel`
179
- : `press ${chalk_1.default.bold('Enter')} for more options`;
169
+ async promptForOTPAsync() {
170
+ const enterMessage = `press ${chalk_1.default.bold('Enter')} to cancel`;
180
171
  const { otp } = await (0, prompts_1.promptAsync)({
181
172
  type: 'text',
182
173
  name: 'otp',
@@ -188,84 +179,11 @@ class SessionManager {
188
179
  return otp;
189
180
  }
190
181
  /**
191
- * Prompt for user to choose a backup OTP method. If selected method is SMS, a request
192
- * for a new OTP will be sent to that method. Then, prompt for the OTP, and retry the user login.
182
+ * Handle the special case error indicating that a second-factor is required for authentication.
193
183
  */
194
- async promptForBackupOTPAsync(username, password, secondFactorDevices) {
195
- const nonPrimarySecondFactorDevices = secondFactorDevices.filter(device => !device.is_primary);
196
- if (nonPrimarySecondFactorDevices.length === 0) {
197
- throw new Error('No other second-factor devices set up. Ensure you have set up and certified a backup device.');
198
- }
199
- const hasAuthenticatorSecondFactorDevice = nonPrimarySecondFactorDevices.find(device => device.method === UserSecondFactorDeviceMethod.AUTHENTICATOR);
200
- const smsNonPrimarySecondFactorDevices = nonPrimarySecondFactorDevices.filter(device => device.method === UserSecondFactorDeviceMethod.SMS);
201
- const authenticatorChoiceSentinel = -1;
202
- const cancelChoiceSentinel = -2;
203
- const deviceChoices = smsNonPrimarySecondFactorDevices.map((device, idx) => ({
204
- title: device.sms_phone_number,
205
- value: idx,
206
- }));
207
- if (hasAuthenticatorSecondFactorDevice) {
208
- deviceChoices.push({
209
- title: 'Authenticator',
210
- value: authenticatorChoiceSentinel,
211
- });
212
- }
213
- deviceChoices.push({
214
- title: 'Cancel',
215
- value: cancelChoiceSentinel,
216
- });
217
- const selectedValue = await (0, prompts_1.selectAsync)('Select a second-factor device:', deviceChoices);
218
- if (selectedValue === cancelChoiceSentinel) {
219
- return null;
220
- }
221
- else if (selectedValue === authenticatorChoiceSentinel) {
222
- return await this.promptForOTPAsync('cancel');
223
- }
224
- const device = smsNonPrimarySecondFactorDevices[selectedValue];
225
- // this is a logged-out endpoint
226
- const apiV2Client = new api_1.ApiV2Client({ accessToken: null, sessionSecret: null });
227
- await apiV2Client.postAsync('auth/send-sms-otp', {
228
- body: {
229
- username,
230
- password,
231
- secondFactorDeviceID: device.id,
232
- },
233
- });
234
- return await this.promptForOTPAsync('cancel');
235
- }
236
- /**
237
- * Handle the special case error indicating that a second-factor is required for
238
- * authentication.
239
- *
240
- * There are three cases we need to handle:
241
- * 1. User's primary second-factor device was SMS, OTP was automatically sent by the server to that
242
- * device already. In this case we should just prompt for the SMS OTP (or backup code), which the
243
- * user should be receiving shortly. We should give the user a way to cancel and the prompt and move
244
- * to case 3 below.
245
- * 2. User's primary second-factor device is authenticator. In this case we should prompt for authenticator
246
- * OTP (or backup code) and also give the user a way to cancel and move to case 3 below.
247
- * 3. User doesn't have a primary device or doesn't have access to their primary device. In this case
248
- * we should show a picker of the SMS devices that they can have an OTP code sent to, and when
249
- * the user picks one we show a prompt() for the sent OTP.
250
- */
251
- async retryUsernamePasswordAuthWithOTPAsync(username, password, metadata) {
252
- const { secondFactorDevices, smsAutomaticallySent } = metadata;
253
- (0, assert_1.default)(secondFactorDevices !== undefined && smsAutomaticallySent !== undefined, `Malformed OTP error metadata: ${metadata}`);
254
- const primaryDevice = secondFactorDevices.find(device => device.is_primary);
255
- let otp = null;
256
- if (smsAutomaticallySent) {
257
- (0, assert_1.default)(primaryDevice, 'OTP should only automatically be sent when there is a primary device');
258
- log_1.default.log(`One-time password was sent to the phone number ending in ${primaryDevice.sms_phone_number}.`);
259
- otp = await this.promptForOTPAsync('menu');
260
- }
261
- if (primaryDevice?.method === UserSecondFactorDeviceMethod.AUTHENTICATOR) {
262
- log_1.default.log('One-time password from authenticator required.');
263
- otp = await this.promptForOTPAsync('menu');
264
- }
265
- // user bailed on case 1 or 2, wants to move to case 3
266
- if (!otp) {
267
- otp = await this.promptForBackupOTPAsync(username, password, secondFactorDevices);
268
- }
184
+ async retryUsernamePasswordAuthWithOTPAsync(username, password) {
185
+ log_1.default.log('One-time password from authenticator required.');
186
+ const otp = await this.promptForOTPAsync();
269
187
  if (!otp) {
270
188
  throw new Error('Cancelled login');
271
189
  }