eas-cli 18.7.0 → 18.8.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 (49) hide show
  1. package/README.md +147 -90
  2. package/build/build/utils/url.d.ts +6 -0
  3. package/build/build/utils/url.js +9 -0
  4. package/build/channel/insights/formatInsights.d.ts +47 -0
  5. package/build/channel/insights/formatInsights.js +108 -0
  6. package/build/commands/channel/insights.d.ts +18 -0
  7. package/build/commands/channel/insights.js +71 -0
  8. package/build/commands/observe/events.d.ts +1 -0
  9. package/build/commands/observe/events.js +17 -4
  10. package/build/commands/observe/metrics.d.ts +1 -0
  11. package/build/commands/observe/metrics.js +18 -6
  12. package/build/commands/observe/versions.d.ts +1 -0
  13. package/build/commands/observe/versions.js +17 -4
  14. package/build/commands/simulator/start.d.ts +16 -0
  15. package/build/commands/simulator/start.js +203 -0
  16. package/build/commands/update/insights.d.ts +19 -0
  17. package/build/commands/update/insights.js +75 -0
  18. package/build/commands/update/view.d.ts +4 -0
  19. package/build/commands/update/view.js +47 -2
  20. package/build/credentials/ios/appstore/capabilityIdentifiers.js +28 -3
  21. package/build/graphql/client.d.ts +13 -0
  22. package/build/graphql/client.js +36 -1
  23. package/build/graphql/generated.d.ts +318 -0
  24. package/build/graphql/generated.js +21 -3
  25. package/build/graphql/mutations/DeviceRunSessionMutation.d.ts +5 -0
  26. package/build/graphql/mutations/DeviceRunSessionMutation.js +34 -0
  27. package/build/graphql/queries/ChannelInsightsQuery.d.ts +12 -0
  28. package/build/graphql/queries/ChannelInsightsQuery.js +81 -0
  29. package/build/graphql/queries/DeviceRunSessionQuery.d.ts +5 -0
  30. package/build/graphql/queries/DeviceRunSessionQuery.js +28 -0
  31. package/build/graphql/queries/UpdateInsightsQuery.d.ts +10 -0
  32. package/build/graphql/queries/UpdateInsightsQuery.js +53 -0
  33. package/build/graphql/types/Observe.js +1 -0
  34. package/build/insights/formatTimespan.d.ts +7 -0
  35. package/build/insights/formatTimespan.js +15 -0
  36. package/build/insights/timeRange.d.ts +10 -0
  37. package/build/insights/timeRange.js +10 -0
  38. package/build/metadata/apple/tasks/previews.js +41 -15
  39. package/build/observe/formatEvents.d.ts +3 -0
  40. package/build/observe/formatEvents.js +4 -0
  41. package/build/observe/formatMetrics.d.ts +3 -2
  42. package/build/observe/formatMetrics.js +16 -27
  43. package/build/observe/formatVersions.js +2 -8
  44. package/build/update/insights/formatInsights.d.ts +34 -0
  45. package/build/update/insights/formatInsights.js +128 -0
  46. package/build/utils/renderTextTable.d.ts +6 -0
  47. package/build/utils/renderTextTable.js +23 -0
  48. package/oclif.manifest.json +995 -593
  49. package/package.json +5 -5
@@ -5,5 +5,11 @@ export declare function getArtifactUrl(artifactId: string): string;
5
5
  export declare function getInternalDistributionInstallUrl(build: BuildFragment): string;
6
6
  export declare function getUpdateGroupUrl(accountName: string, projectName: string, updateGroupId: string): string;
7
7
  export declare function getWorkflowRunUrl(accountName: string, projectName: string, workflowRunId: string): string;
8
+ /**
9
+ * @deprecated Links to the raw job-run page; prefer a higher-level URL (e.g. the workflow run
10
+ * or the feature-specific dashboard) that gives users more context. Use this only for internal
11
+ * tooling where no richer URL exists.
12
+ */
13
+ export declare function getBareJobRunUrl(accountName: string, projectName: string, jobRunId: string): string;
8
14
  export declare function getProjectGitHubSettingsUrl(accountName: string, projectName: string): string;
9
15
  export declare function getHostingDeploymentsUrl(accountName: string, projectName: string): string;
@@ -6,6 +6,7 @@ exports.getArtifactUrl = getArtifactUrl;
6
6
  exports.getInternalDistributionInstallUrl = getInternalDistributionInstallUrl;
7
7
  exports.getUpdateGroupUrl = getUpdateGroupUrl;
8
8
  exports.getWorkflowRunUrl = getWorkflowRunUrl;
9
+ exports.getBareJobRunUrl = getBareJobRunUrl;
9
10
  exports.getProjectGitHubSettingsUrl = getProjectGitHubSettingsUrl;
10
11
  exports.getHostingDeploymentsUrl = getHostingDeploymentsUrl;
11
12
  const tslib_1 = require("tslib");
@@ -38,6 +39,14 @@ function getUpdateGroupUrl(accountName, projectName, updateGroupId) {
38
39
  function getWorkflowRunUrl(accountName, projectName, workflowRunId) {
39
40
  return new URL(`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(projectName)}/workflows/${workflowRunId}`, (0, api_1.getExpoWebsiteBaseUrl)()).toString();
40
41
  }
42
+ /**
43
+ * @deprecated Links to the raw job-run page; prefer a higher-level URL (e.g. the workflow run
44
+ * or the feature-specific dashboard) that gives users more context. Use this only for internal
45
+ * tooling where no richer URL exists.
46
+ */
47
+ function getBareJobRunUrl(accountName, projectName, jobRunId) {
48
+ return new URL(`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(projectName)}/job-runs/${encodeURIComponent(jobRunId)}`, (0, api_1.getExpoWebsiteBaseUrl)()).toString();
49
+ }
41
50
  function getProjectGitHubSettingsUrl(accountName, projectName) {
42
51
  return new URL(`/accounts/${encodeURIComponent(accountName)}/projects/${encodeURIComponent(projectName)}/github`, (0, api_1.getExpoWebsiteBaseUrl)()).toString();
43
52
  }
@@ -0,0 +1,47 @@
1
+ import { ChannelRuntimeInsights } from '../../graphql/queries/ChannelInsightsQuery';
2
+ export interface ChannelInsightsTimespan {
3
+ startTime: string;
4
+ endTime: string;
5
+ daysBack?: number;
6
+ }
7
+ export interface ChannelMostPopularUpdate {
8
+ rank: number;
9
+ groupId: string;
10
+ message: string | null;
11
+ platform: string;
12
+ totalUniqueUsers: number;
13
+ }
14
+ export interface ChannelInsightsSummary {
15
+ channelName: string;
16
+ runtimeVersion: string;
17
+ startTime: string;
18
+ endTime: string;
19
+ daysBack?: number;
20
+ embeddedUpdateTotalUniqueUsers: number;
21
+ otaTotalUniqueUsers: number;
22
+ mostPopularUpdates: ChannelMostPopularUpdate[];
23
+ cumulativeMetricsAtLastTimestamp: {
24
+ id: string;
25
+ label: string;
26
+ data: number;
27
+ }[];
28
+ uniqueUsersOverTime: {
29
+ labels: string[];
30
+ datasets: {
31
+ id: string;
32
+ label: string;
33
+ data: (number | null)[];
34
+ }[];
35
+ };
36
+ cumulativeMetricsOverTime: {
37
+ labels: string[];
38
+ datasets: {
39
+ id: string;
40
+ label: string;
41
+ data: (number | null)[];
42
+ }[];
43
+ };
44
+ }
45
+ export declare function toChannelInsightsSummary(channelName: string, runtimeVersion: string, insights: ChannelRuntimeInsights, timespan: ChannelInsightsTimespan): ChannelInsightsSummary;
46
+ export declare function buildChannelInsightsJson(summary: ChannelInsightsSummary): object;
47
+ export declare function buildChannelInsightsTable(summary: ChannelInsightsSummary): string;
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toChannelInsightsSummary = toChannelInsightsSummary;
4
+ exports.buildChannelInsightsJson = buildChannelInsightsJson;
5
+ exports.buildChannelInsightsTable = buildChannelInsightsTable;
6
+ const tslib_1 = require("tslib");
7
+ const chalk_1 = tslib_1.__importDefault(require("chalk"));
8
+ const formatTimespan_1 = require("../../insights/formatTimespan");
9
+ const formatFields_1 = tslib_1.__importDefault(require("../../utils/formatFields"));
10
+ const renderTextTable_1 = tslib_1.__importDefault(require("../../utils/renderTextTable"));
11
+ function toChannelInsightsSummary(channelName, runtimeVersion, insights, timespan) {
12
+ const mostPopular = insights.mostPopularUpdates.map((u, i) => ({
13
+ rank: i + 1,
14
+ groupId: u.group,
15
+ message: u.message ?? null,
16
+ platform: u.platform,
17
+ totalUniqueUsers: u.insights.totalUniqueUsers,
18
+ }));
19
+ const otaTotalUniqueUsers = mostPopular.reduce((sum, u) => sum + u.totalUniqueUsers, 0);
20
+ return {
21
+ channelName,
22
+ runtimeVersion,
23
+ startTime: timespan.startTime,
24
+ endTime: timespan.endTime,
25
+ daysBack: timespan.daysBack,
26
+ embeddedUpdateTotalUniqueUsers: insights.embeddedUpdateTotalUniqueUsers,
27
+ otaTotalUniqueUsers,
28
+ mostPopularUpdates: mostPopular,
29
+ cumulativeMetricsAtLastTimestamp: insights.cumulativeMetricsOverTime.metricsAtLastTimestamp.map(m => ({ id: m.id, label: m.label, data: m.data })),
30
+ uniqueUsersOverTime: {
31
+ labels: insights.uniqueUsersOverTime.data.labels,
32
+ datasets: insights.uniqueUsersOverTime.data.datasets.map(d => ({
33
+ id: d.id,
34
+ label: d.label,
35
+ data: d.data,
36
+ })),
37
+ },
38
+ cumulativeMetricsOverTime: {
39
+ labels: insights.cumulativeMetricsOverTime.data.labels,
40
+ datasets: insights.cumulativeMetricsOverTime.data.datasets.map(d => ({
41
+ id: d.id,
42
+ label: d.label,
43
+ data: d.data,
44
+ })),
45
+ },
46
+ };
47
+ }
48
+ function buildChannelInsightsJson(summary) {
49
+ return {
50
+ channel: summary.channelName,
51
+ runtimeVersion: summary.runtimeVersion,
52
+ timespan: {
53
+ start: summary.startTime,
54
+ end: summary.endTime,
55
+ ...(summary.daysBack !== undefined ? { daysBack: summary.daysBack } : {}),
56
+ },
57
+ embeddedUpdateTotalUniqueUsers: summary.embeddedUpdateTotalUniqueUsers,
58
+ otaTotalUniqueUsers: summary.otaTotalUniqueUsers,
59
+ mostPopularUpdates: summary.mostPopularUpdates,
60
+ cumulativeMetricsAtLastTimestamp: summary.cumulativeMetricsAtLastTimestamp,
61
+ uniqueUsersOverTime: summary.uniqueUsersOverTime,
62
+ cumulativeMetricsOverTime: summary.cumulativeMetricsOverTime,
63
+ };
64
+ }
65
+ function buildChannelInsightsTable(summary) {
66
+ const sections = [];
67
+ sections.push(chalk_1.default.bold('Channel insights:'));
68
+ sections.push((0, formatFields_1.default)([
69
+ { label: 'Channel', value: summary.channelName },
70
+ { label: 'Runtime version', value: summary.runtimeVersion },
71
+ { label: 'Time range', value: (0, formatTimespan_1.formatTimespan)(summary) },
72
+ {
73
+ label: 'Embedded update users',
74
+ value: summary.embeddedUpdateTotalUniqueUsers.toLocaleString(),
75
+ },
76
+ { label: 'OTA update users', value: summary.otaTotalUniqueUsers.toLocaleString() },
77
+ ]));
78
+ if (summary.cumulativeMetricsAtLastTimestamp.length > 0) {
79
+ sections.push('');
80
+ sections.push(chalk_1.default.bold('Cumulative metrics at last timestamp:'));
81
+ sections.push((0, formatFields_1.default)(summary.cumulativeMetricsAtLastTimestamp.map(m => ({
82
+ label: m.label,
83
+ value: m.data.toLocaleString(),
84
+ }))));
85
+ }
86
+ if (summary.mostPopularUpdates.length > 0) {
87
+ sections.push('');
88
+ sections.push(chalk_1.default.bold(`Most popular updates${formatTrailingTimespan(summary)}:`));
89
+ sections.push(renderMostPopularTable(summary.mostPopularUpdates));
90
+ }
91
+ else {
92
+ sections.push('');
93
+ sections.push(chalk_1.default.yellow('No update launches recorded for this channel and runtime.'));
94
+ }
95
+ return sections.join('\n');
96
+ }
97
+ function formatTrailingTimespan(summary) {
98
+ return summary.daysBack ? ` (last ${summary.daysBack} days)` : '';
99
+ }
100
+ function renderMostPopularTable(rows) {
101
+ return (0, renderTextTable_1.default)(['#', 'Group ID', 'Platform', 'Unique users', 'Message'], rows.map(r => [
102
+ String(r.rank),
103
+ r.groupId,
104
+ r.platform,
105
+ r.totalUniqueUsers.toLocaleString(),
106
+ r.message ?? '',
107
+ ]));
108
+ }
@@ -0,0 +1,18 @@
1
+ import EasCommand from '../../commandUtils/EasCommand';
2
+ export default class ChannelInsights extends EasCommand {
3
+ static description: string;
4
+ static flags: {
5
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
6
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ channel: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ 'runtime-version': import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ days: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ start: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ end: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ };
13
+ static contextDefinition: {
14
+ loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
15
+ projectId: import("../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
16
+ };
17
+ runAsync(): Promise<void>;
18
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const core_1 = require("@oclif/core");
5
+ const formatInsights_1 = require("../../channel/insights/formatInsights");
6
+ const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
7
+ const flags_1 = require("../../commandUtils/flags");
8
+ const ChannelInsightsQuery_1 = require("../../graphql/queries/ChannelInsightsQuery");
9
+ const timeRange_1 = require("../../insights/timeRange");
10
+ const log_1 = tslib_1.__importDefault(require("../../log"));
11
+ const json_1 = require("../../utils/json");
12
+ class ChannelInsights extends EasCommand_1.default {
13
+ static description = 'display adoption, crash, and unique-user insights for a channel + runtime version';
14
+ static flags = {
15
+ channel: core_1.Flags.string({
16
+ description: 'Name of the channel.',
17
+ required: true,
18
+ }),
19
+ 'runtime-version': core_1.Flags.string({
20
+ description: 'Runtime version to query insights for.',
21
+ required: true,
22
+ }),
23
+ days: core_1.Flags.integer({
24
+ description: 'Show insights from the last N days (default 7, mutually exclusive with --start/--end).',
25
+ min: 1,
26
+ exclusive: ['start', 'end'],
27
+ }),
28
+ start: core_1.Flags.string({
29
+ description: 'Start of insights time range (ISO date).',
30
+ exclusive: ['days'],
31
+ }),
32
+ end: core_1.Flags.string({
33
+ description: 'End of insights time range (ISO date).',
34
+ exclusive: ['days'],
35
+ }),
36
+ ...flags_1.EasNonInteractiveAndJsonFlags,
37
+ };
38
+ static contextDefinition = {
39
+ ...this.ContextOptions.ProjectId,
40
+ ...this.ContextOptions.LoggedIn,
41
+ };
42
+ async runAsync() {
43
+ const { flags } = await this.parse(ChannelInsights);
44
+ const { json, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
45
+ const { projectId, loggedIn: { graphqlClient }, } = await this.getContextAsync(ChannelInsights, { nonInteractive });
46
+ if (json) {
47
+ (0, json_1.enableJsonOutput)();
48
+ }
49
+ const { daysBack, startTime, endTime } = (0, timeRange_1.resolveInsightsTimeRange)(flags);
50
+ const insights = await ChannelInsightsQuery_1.ChannelInsightsQuery.viewChannelRuntimeInsightsAsync(graphqlClient, {
51
+ appId: projectId,
52
+ channelName: flags.channel,
53
+ runtimeVersion: flags['runtime-version'],
54
+ startTime,
55
+ endTime,
56
+ });
57
+ const summary = (0, formatInsights_1.toChannelInsightsSummary)(flags.channel, flags['runtime-version'], insights, {
58
+ startTime,
59
+ endTime,
60
+ daysBack,
61
+ });
62
+ if (json) {
63
+ (0, json_1.printJsonOnlyOutput)((0, formatInsights_1.buildChannelInsightsJson)(summary));
64
+ }
65
+ else {
66
+ log_1.default.addNewLineIfNone();
67
+ log_1.default.log((0, formatInsights_1.buildChannelInsightsTable)(summary));
68
+ }
69
+ }
70
+ }
71
+ exports.default = ChannelInsights;
@@ -23,5 +23,6 @@ export default class ObserveEvents extends EasCommand {
23
23
  loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
24
24
  projectId: import("../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
25
25
  };
26
+ private static loggedInOnlyContextDefinition;
26
27
  runAsync(): Promise<void>;
27
28
  }
@@ -71,12 +71,25 @@ class ObserveEvents extends EasCommand_1.default {
71
71
  ...this.ContextOptions.ProjectId,
72
72
  ...this.ContextOptions.LoggedIn,
73
73
  };
74
+ static loggedInOnlyContextDefinition = {
75
+ ...this.ContextOptions.LoggedIn,
76
+ };
74
77
  async runAsync() {
75
78
  const { flags, args } = await this.parse(ObserveEvents);
76
- const { projectId: contextProjectId, loggedIn: { graphqlClient }, } = await this.getContextAsync(ObserveEvents, {
77
- nonInteractive: flags['non-interactive'],
78
- });
79
- const projectId = flags['project-id'] ?? contextProjectId;
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
93
  if (flags.json) {
81
94
  (0, json_1.enableJsonOutput)();
82
95
  }
@@ -17,5 +17,6 @@ export default class ObserveMetrics extends EasCommand {
17
17
  loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
18
18
  projectId: import("../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
19
19
  };
20
+ private static loggedInOnlyContextDefinition;
20
21
  runAsync(): Promise<void>;
21
22
  }
@@ -69,12 +69,25 @@ class ObserveMetrics extends EasCommand_1.default {
69
69
  ...this.ContextOptions.ProjectId,
70
70
  ...this.ContextOptions.LoggedIn,
71
71
  };
72
+ static loggedInOnlyContextDefinition = {
73
+ ...this.ContextOptions.LoggedIn,
74
+ };
72
75
  async runAsync() {
73
76
  const { flags } = await this.parse(ObserveMetrics);
74
- const { projectId: contextProjectId, loggedIn: { graphqlClient }, } = await this.getContextAsync(ObserveMetrics, {
75
- nonInteractive: flags['non-interactive'],
76
- });
77
- const projectId = flags['project-id'] ?? contextProjectId;
77
+ let projectId;
78
+ let graphqlClient;
79
+ if (flags['project-id']) {
80
+ projectId = flags['project-id'];
81
+ const ctx = await this.getContextAsync({ contextDefinition: ObserveMetrics.loggedInOnlyContextDefinition }, { nonInteractive: flags['non-interactive'] });
82
+ graphqlClient = ctx.loggedIn.graphqlClient;
83
+ }
84
+ else {
85
+ const ctx = await this.getContextAsync(ObserveMetrics, {
86
+ nonInteractive: flags['non-interactive'],
87
+ });
88
+ projectId = ctx.projectId;
89
+ graphqlClient = ctx.loggedIn.graphqlClient;
90
+ }
78
91
  if (flags.json) {
79
92
  (0, json_1.enableJsonOutput)();
80
93
  }
@@ -94,7 +107,7 @@ class ObserveMetrics extends EasCommand_1.default {
94
107
  : undefined;
95
108
  if (flags.json) {
96
109
  const stats = argumentsStat ?? DEFAULT_STATS_JSON;
97
- (0, json_1.printJsonOnlyOutput)((0, formatMetrics_1.buildObserveMetricsJson)(metricsMap, metricNames, stats, totalEventCounts));
110
+ (0, json_1.printJsonOnlyOutput)((0, formatMetrics_1.buildObserveMetricsJson)(metricsMap, metricNames, stats, totalEventCounts, buildNumbersMap, updateIdsMap));
98
111
  }
99
112
  else {
100
113
  const stats = argumentsStat ?? DEFAULT_STATS_TABLE;
@@ -102,7 +115,6 @@ class ObserveMetrics extends EasCommand_1.default {
102
115
  log_1.default.log((0, formatMetrics_1.buildObserveMetricsTable)(metricsMap, metricNames, stats, {
103
116
  daysBack,
104
117
  buildNumbersMap,
105
- updateIdsMap,
106
118
  totalEventCounts,
107
119
  }));
108
120
  }
@@ -15,5 +15,6 @@ export default class ObserveVersions extends EasCommand {
15
15
  loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
16
16
  projectId: import("../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
17
17
  };
18
+ private static loggedInOnlyContextDefinition;
18
19
  runAsync(): Promise<void>;
19
20
  }
@@ -40,12 +40,25 @@ class ObserveVersions extends EasCommand_1.default {
40
40
  ...this.ContextOptions.ProjectId,
41
41
  ...this.ContextOptions.LoggedIn,
42
42
  };
43
+ static loggedInOnlyContextDefinition = {
44
+ ...this.ContextOptions.LoggedIn,
45
+ };
43
46
  async runAsync() {
44
47
  const { flags } = await this.parse(ObserveVersions);
45
- const { projectId: contextProjectId, loggedIn: { graphqlClient }, } = await this.getContextAsync(ObserveVersions, {
46
- nonInteractive: flags['non-interactive'],
47
- });
48
- const projectId = flags['project-id'] ?? contextProjectId;
48
+ let projectId;
49
+ let graphqlClient;
50
+ if (flags['project-id']) {
51
+ projectId = flags['project-id'];
52
+ const ctx = await this.getContextAsync({ contextDefinition: ObserveVersions.loggedInOnlyContextDefinition }, { nonInteractive: flags['non-interactive'] });
53
+ graphqlClient = ctx.loggedIn.graphqlClient;
54
+ }
55
+ else {
56
+ const ctx = await this.getContextAsync(ObserveVersions, {
57
+ nonInteractive: flags['non-interactive'],
58
+ });
59
+ projectId = ctx.projectId;
60
+ graphqlClient = ctx.loggedIn.graphqlClient;
61
+ }
49
62
  if (flags.json) {
50
63
  (0, json_1.enableJsonOutput)();
51
64
  }
@@ -0,0 +1,16 @@
1
+ import EasCommand from '../../commandUtils/EasCommand';
2
+ export default class SimulatorStart extends EasCommand {
3
+ static hidden: boolean;
4
+ static description: string;
5
+ static flags: {
6
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
7
+ platform: import("@oclif/core/lib/interfaces").OptionFlag<"android" | "ios", import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ type: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
9
+ 'package-version': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
10
+ };
11
+ static contextDefinition: {
12
+ loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
13
+ projectId: import("../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
14
+ };
15
+ runAsync(): Promise<void>;
16
+ }
@@ -0,0 +1,203 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const tslib_1 = require("tslib");
4
+ const core_1 = require("@oclif/core");
5
+ const url_1 = require("../../build/utils/url");
6
+ const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
7
+ const flags_1 = require("../../commandUtils/flags");
8
+ const generated_1 = require("../../graphql/generated");
9
+ const DeviceRunSessionMutation_1 = require("../../graphql/mutations/DeviceRunSessionMutation");
10
+ const DeviceRunSessionQuery_1 = require("../../graphql/queries/DeviceRunSessionQuery");
11
+ const log_1 = tslib_1.__importStar(require("../../log"));
12
+ const ora_1 = require("../../ora");
13
+ const promise_1 = require("../../utils/promise");
14
+ const nullthrows_1 = tslib_1.__importDefault(require("nullthrows"));
15
+ const POLL_INTERVAL_MS = 5_000; // 5 seconds
16
+ const POLL_TIMEOUT_MS = 15 * 60 * 1_000; // 15 minutes
17
+ // Mapping enum → CLI flag value. Declared as Record<DeviceRunSessionType, string>
18
+ // so adding a new enum value in codegen fails the build until it is wired up here.
19
+ const DEVICE_RUN_SESSION_TYPE_FLAG_VALUES = {
20
+ [generated_1.DeviceRunSessionType.AgentDevice]: 'agent-device',
21
+ };
22
+ const DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE = Object.fromEntries(Object.entries(DEVICE_RUN_SESSION_TYPE_FLAG_VALUES).map(([type, value]) => [value, type]));
23
+ class SimulatorStart extends EasCommand_1.default {
24
+ static hidden = true;
25
+ static description = '[EXPERIMENTAL] start a remote simulator session on EAS and get the credentials to connect to it with the CLI tool of your choice';
26
+ static flags = {
27
+ platform: core_1.Flags.option({
28
+ description: 'Device platform',
29
+ options: ['android', 'ios'],
30
+ required: true,
31
+ })(),
32
+ type: core_1.Flags.option({
33
+ description: 'Type of device run session to create',
34
+ options: Object.values(DEVICE_RUN_SESSION_TYPE_FLAG_VALUES),
35
+ default: DEVICE_RUN_SESSION_TYPE_FLAG_VALUES[generated_1.DeviceRunSessionType.AgentDevice],
36
+ })(),
37
+ 'package-version': core_1.Flags.string({
38
+ description: 'Version of the package backing the device run session (e.g. "0.1.3-alpha.3"). Defaults to "latest" when omitted.',
39
+ }),
40
+ ...flags_1.EASNonInteractiveFlag,
41
+ };
42
+ static contextDefinition = {
43
+ ...this.ContextOptions.ProjectId,
44
+ ...this.ContextOptions.LoggedIn,
45
+ };
46
+ async runAsync() {
47
+ const { flags } = await this.parse(SimulatorStart);
48
+ const { projectId, loggedIn: { graphqlClient }, } = await this.getContextAsync(SimulatorStart, {
49
+ nonInteractive: flags['non-interactive'],
50
+ });
51
+ const platform = flags.platform === 'android' ? generated_1.AppPlatform.Android : generated_1.AppPlatform.Ios;
52
+ const createSpinner = (0, ora_1.ora)('🚀 Creating device run session').start();
53
+ let deviceRunSessionId;
54
+ try {
55
+ const session = await DeviceRunSessionMutation_1.DeviceRunSessionMutation.createDeviceRunSessionAsync(graphqlClient, {
56
+ appId: projectId,
57
+ platform,
58
+ type: DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE[flags.type],
59
+ packageVersion: flags['package-version'],
60
+ });
61
+ deviceRunSessionId = session.id;
62
+ const jobRunId = (0, nullthrows_1.default)(session.turtleJobRun?.id, 'Expected device run session to start');
63
+ const jobRunUrl = (0, url_1.getBareJobRunUrl)(session.app.ownerAccount.name, session.app.slug, jobRunId);
64
+ createSpinner.succeed(`Device run session created (id: ${deviceRunSessionId}) ${(0, log_1.link)(jobRunUrl)}`);
65
+ }
66
+ catch (err) {
67
+ createSpinner.fail('Failed to create device run session');
68
+ throw err;
69
+ }
70
+ const checkReadiness = getReadinessCheckerForType(flags.type);
71
+ const pollSpinner = (0, ora_1.ora)(`⏳ Waiting for ${flags.type} daemon to start`).start();
72
+ const deadline = Date.now() + POLL_TIMEOUT_MS;
73
+ let result = { ready: false };
74
+ try {
75
+ while (Date.now() < deadline) {
76
+ const session = await DeviceRunSessionQuery_1.DeviceRunSessionQuery.byIdAsync(graphqlClient, deviceRunSessionId);
77
+ if (session.status === generated_1.DeviceRunSessionStatus.Errored ||
78
+ session.status === generated_1.DeviceRunSessionStatus.Stopped) {
79
+ throw new Error(`Device run session ${deviceRunSessionId} ${session.status.toLowerCase()} before the ${flags.type} daemon was ready.`);
80
+ }
81
+ const jobRunStatus = session.turtleJobRun?.status;
82
+ if (jobRunStatus === generated_1.JobRunStatus.Errored ||
83
+ jobRunStatus === generated_1.JobRunStatus.Canceled ||
84
+ jobRunStatus === generated_1.JobRunStatus.Finished) {
85
+ throw new Error(`Turtle job run for device run session ${deviceRunSessionId} ${jobRunStatus.toLowerCase()} before the ${flags.type} daemon was ready.`);
86
+ }
87
+ const logMessages = await fetchLogMessagesAsync(session.turtleJobRun?.logFileUrls ?? []);
88
+ result = checkReadiness(logMessages);
89
+ if (result.ready) {
90
+ pollSpinner.succeed(`🎉 ${flags.type} daemon is ready`);
91
+ break;
92
+ }
93
+ await (0, promise_1.sleepAsync)(POLL_INTERVAL_MS);
94
+ }
95
+ }
96
+ catch (err) {
97
+ pollSpinner.fail(`Failed while polling for ${flags.type} daemon logs`);
98
+ throw err;
99
+ }
100
+ if (!result.ready) {
101
+ pollSpinner.fail(`Timed out waiting for ${flags.type} daemon to start`);
102
+ throw new Error(`Timed out after ${Math.round(POLL_TIMEOUT_MS / 1000)}s waiting for ${flags.type} daemon to start.`);
103
+ }
104
+ log_1.default.newLine();
105
+ log_1.default.log(`🔑 Run the following in your shell to attach to ${flags.type}:`);
106
+ log_1.default.newLine();
107
+ log_1.default.log(result.message);
108
+ }
109
+ }
110
+ exports.default = SimulatorStart;
111
+ function getReadinessCheckerForType(type) {
112
+ switch (type) {
113
+ case DEVICE_RUN_SESSION_TYPE_FLAG_VALUES[generated_1.DeviceRunSessionType.AgentDevice]:
114
+ return checkAgentDeviceReadiness;
115
+ default:
116
+ throw new Error(`Unsupported device run session type: ${type}`);
117
+ }
118
+ }
119
+ const AGENT_DEVICE_BASE_URL_ENV_VAR = 'AGENT_DEVICE_DAEMON_BASE_URL';
120
+ const AGENT_DEVICE_AUTH_TOKEN_ENV_VAR = 'AGENT_DEVICE_DAEMON_AUTH_TOKEN';
121
+ function checkAgentDeviceReadiness(logMessages) {
122
+ let baseUrl;
123
+ let authToken;
124
+ for (const msg of logMessages) {
125
+ baseUrl = baseUrl ?? extractExportedEnvValue(msg, AGENT_DEVICE_BASE_URL_ENV_VAR);
126
+ authToken = authToken ?? extractExportedEnvValue(msg, AGENT_DEVICE_AUTH_TOKEN_ENV_VAR);
127
+ if (baseUrl && authToken) {
128
+ break;
129
+ }
130
+ }
131
+ if (baseUrl && authToken) {
132
+ return {
133
+ ready: true,
134
+ message: [
135
+ `export ${AGENT_DEVICE_BASE_URL_ENV_VAR}='${baseUrl}'`,
136
+ `export ${AGENT_DEVICE_AUTH_TOKEN_ENV_VAR}='${authToken}'`,
137
+ ].join('\n'),
138
+ };
139
+ }
140
+ return { ready: false };
141
+ }
142
+ async function fetchLogMessagesAsync(logUrls) {
143
+ const messages = [];
144
+ for (const url of logUrls) {
145
+ const text = await fetchLogTextAsync(url);
146
+ if (!text) {
147
+ continue;
148
+ }
149
+ for (const line of text.split('\n')) {
150
+ if (!line.trim()) {
151
+ continue;
152
+ }
153
+ messages.push(extractLogMessage(line));
154
+ }
155
+ }
156
+ return messages;
157
+ }
158
+ async function fetchLogTextAsync(url) {
159
+ try {
160
+ const response = await fetch(url);
161
+ if (!response.ok) {
162
+ return undefined;
163
+ }
164
+ return await response.text();
165
+ }
166
+ catch {
167
+ return undefined;
168
+ }
169
+ }
170
+ function extractLogMessage(line) {
171
+ // Turtle job run logs are JSONL (bunyan-shaped), e.g.
172
+ // {"msg":"export FOO=\"bar\"","time":"...","logId":"..."}
173
+ // Fall back to the raw line if it's not JSON or doesn't have a string msg.
174
+ const trimmed = line.trim();
175
+ if (!trimmed.startsWith('{')) {
176
+ return line;
177
+ }
178
+ try {
179
+ const parsed = JSON.parse(trimmed);
180
+ if (parsed && typeof parsed === 'object' && 'msg' in parsed) {
181
+ const msg = parsed.msg;
182
+ if (typeof msg === 'string') {
183
+ return msg;
184
+ }
185
+ }
186
+ }
187
+ catch {
188
+ // not JSON, fall through
189
+ }
190
+ return line;
191
+ }
192
+ function extractExportedEnvValue(text, varName) {
193
+ // Matches: export NAME=value | export NAME="value" | export NAME='value'
194
+ const pattern = new RegExp(`export\\s+${escapeRegExp(varName)}=(?:"([^"]*)"|'([^']*)'|(\\S+))`);
195
+ const match = pattern.exec(text);
196
+ if (!match) {
197
+ return undefined;
198
+ }
199
+ return match[1] ?? match[2] ?? match[3];
200
+ }
201
+ function escapeRegExp(value) {
202
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
203
+ }
@@ -0,0 +1,19 @@
1
+ import EasCommand from '../../commandUtils/EasCommand';
2
+ export default class UpdateInsights extends EasCommand {
3
+ static description: string;
4
+ static args: {
5
+ groupId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
6
+ };
7
+ static flags: {
8
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ 'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
10
+ platform: import("@oclif/core/lib/interfaces").OptionFlag<"android" | "ios" | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
11
+ days: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
12
+ start: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
13
+ end: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
14
+ };
15
+ static contextDefinition: {
16
+ loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
17
+ };
18
+ runAsync(): Promise<void>;
19
+ }