eas-cli 18.7.0 → 18.8.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.
- package/README.md +147 -90
- package/build/channel/insights/formatInsights.d.ts +47 -0
- package/build/channel/insights/formatInsights.js +108 -0
- package/build/commands/channel/insights.d.ts +18 -0
- package/build/commands/channel/insights.js +71 -0
- package/build/commands/observe/events.d.ts +1 -0
- package/build/commands/observe/events.js +17 -4
- package/build/commands/observe/metrics.d.ts +1 -0
- package/build/commands/observe/metrics.js +18 -6
- package/build/commands/observe/versions.d.ts +1 -0
- package/build/commands/observe/versions.js +17 -4
- package/build/commands/update/insights.d.ts +19 -0
- package/build/commands/update/insights.js +75 -0
- package/build/commands/update/view.d.ts +4 -0
- package/build/commands/update/view.js +47 -2
- package/build/credentials/ios/appstore/capabilityIdentifiers.js +28 -3
- package/build/graphql/client.d.ts +13 -0
- package/build/graphql/client.js +36 -1
- package/build/graphql/generated.d.ts +193 -0
- package/build/graphql/generated.js +8 -2
- package/build/graphql/queries/ChannelInsightsQuery.d.ts +12 -0
- package/build/graphql/queries/ChannelInsightsQuery.js +81 -0
- package/build/graphql/queries/UpdateInsightsQuery.d.ts +10 -0
- package/build/graphql/queries/UpdateInsightsQuery.js +53 -0
- package/build/graphql/types/Observe.js +1 -0
- package/build/insights/formatTimespan.d.ts +7 -0
- package/build/insights/formatTimespan.js +15 -0
- package/build/insights/timeRange.d.ts +10 -0
- package/build/insights/timeRange.js +10 -0
- package/build/metadata/apple/tasks/previews.js +41 -15
- package/build/observe/formatEvents.d.ts +3 -0
- package/build/observe/formatEvents.js +4 -0
- package/build/observe/formatMetrics.d.ts +3 -2
- package/build/observe/formatMetrics.js +16 -27
- package/build/observe/formatVersions.js +2 -8
- package/build/update/insights/formatInsights.d.ts +34 -0
- package/build/update/insights/formatInsights.js +128 -0
- package/build/utils/renderTextTable.d.ts +6 -0
- package/build/utils/renderTextTable.js +23 -0
- package/oclif.manifest.json +585 -287
- package/package.json +5 -5
|
@@ -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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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,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
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
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 EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
|
|
6
|
+
const flags_1 = require("../../commandUtils/flags");
|
|
7
|
+
const UpdateInsightsQuery_1 = require("../../graphql/queries/UpdateInsightsQuery");
|
|
8
|
+
const timeRange_1 = require("../../insights/timeRange");
|
|
9
|
+
const log_1 = tslib_1.__importDefault(require("../../log"));
|
|
10
|
+
const formatInsights_1 = require("../../update/insights/formatInsights");
|
|
11
|
+
const json_1 = require("../../utils/json");
|
|
12
|
+
class UpdateInsights extends EasCommand_1.default {
|
|
13
|
+
static description = 'display launch, crash, unique-user, and size insights for an update group';
|
|
14
|
+
static args = {
|
|
15
|
+
groupId: core_1.Args.string({
|
|
16
|
+
required: true,
|
|
17
|
+
description: 'The ID of an update group.',
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
platform: core_1.Flags.option({
|
|
22
|
+
description: 'Filter to a single platform.',
|
|
23
|
+
options: ['ios', 'android'],
|
|
24
|
+
})(),
|
|
25
|
+
days: core_1.Flags.integer({
|
|
26
|
+
description: `Show insights from the last N days (default ${timeRange_1.INSIGHTS_DEFAULT_DAYS_BACK}, mutually exclusive with --start/--end).`,
|
|
27
|
+
min: 1,
|
|
28
|
+
exclusive: ['start', 'end'],
|
|
29
|
+
}),
|
|
30
|
+
start: core_1.Flags.string({
|
|
31
|
+
description: 'Start of insights time range (ISO date).',
|
|
32
|
+
exclusive: ['days'],
|
|
33
|
+
}),
|
|
34
|
+
end: core_1.Flags.string({
|
|
35
|
+
description: 'End of insights time range (ISO date).',
|
|
36
|
+
exclusive: ['days'],
|
|
37
|
+
}),
|
|
38
|
+
...flags_1.EasNonInteractiveAndJsonFlags,
|
|
39
|
+
};
|
|
40
|
+
static contextDefinition = {
|
|
41
|
+
...this.ContextOptions.LoggedIn,
|
|
42
|
+
};
|
|
43
|
+
async runAsync() {
|
|
44
|
+
const { args: { groupId }, flags, } = await this.parse(UpdateInsights);
|
|
45
|
+
const { json, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
|
|
46
|
+
const { loggedIn: { graphqlClient }, } = await this.getContextAsync(UpdateInsights, { nonInteractive });
|
|
47
|
+
if (json) {
|
|
48
|
+
(0, json_1.enableJsonOutput)();
|
|
49
|
+
}
|
|
50
|
+
const { daysBack, startTime, endTime } = (0, timeRange_1.resolveInsightsTimeRange)(flags);
|
|
51
|
+
const allUpdates = await UpdateInsightsQuery_1.UpdateInsightsQuery.viewUpdateGroupInsightsAsync(graphqlClient, {
|
|
52
|
+
groupId,
|
|
53
|
+
startTime,
|
|
54
|
+
endTime,
|
|
55
|
+
});
|
|
56
|
+
const updates = flags.platform
|
|
57
|
+
? allUpdates.filter(u => u.platform === flags.platform)
|
|
58
|
+
: allUpdates;
|
|
59
|
+
if (updates.length === 0) {
|
|
60
|
+
throw new Error(`Update group "${groupId}" has no ${flags.platform} update (available platforms: ${allUpdates
|
|
61
|
+
.map(u => u.platform)
|
|
62
|
+
.sort()
|
|
63
|
+
.join(', ')}).`);
|
|
64
|
+
}
|
|
65
|
+
const summary = (0, formatInsights_1.toUpdateInsightsSummary)(groupId, updates, { startTime, endTime, daysBack });
|
|
66
|
+
if (json) {
|
|
67
|
+
(0, json_1.printJsonOnlyOutput)((0, formatInsights_1.buildUpdateInsightsJson)(summary));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
log_1.default.addNewLineIfNone();
|
|
71
|
+
log_1.default.log((0, formatInsights_1.buildUpdateInsightsTable)(summary));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
exports.default = UpdateInsights;
|
|
@@ -6,6 +6,10 @@ export default class UpdateView extends EasCommand {
|
|
|
6
6
|
};
|
|
7
7
|
static flags: {
|
|
8
8
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
insights: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
10
|
+
days: import("@oclif/core/lib/interfaces").OptionFlag<number | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
start: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
12
|
+
end: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
9
13
|
};
|
|
10
14
|
static contextDefinition: {
|
|
11
15
|
loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
|
|
@@ -5,8 +5,11 @@ const core_1 = require("@oclif/core");
|
|
|
5
5
|
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
6
|
const EasCommand_1 = tslib_1.__importDefault(require("../../commandUtils/EasCommand"));
|
|
7
7
|
const flags_1 = require("../../commandUtils/flags");
|
|
8
|
+
const UpdateInsightsQuery_1 = require("../../graphql/queries/UpdateInsightsQuery");
|
|
8
9
|
const UpdateQuery_1 = require("../../graphql/queries/UpdateQuery");
|
|
10
|
+
const timeRange_1 = require("../../insights/timeRange");
|
|
9
11
|
const log_1 = tslib_1.__importDefault(require("../../log"));
|
|
12
|
+
const formatInsights_1 = require("../../update/insights/formatInsights");
|
|
10
13
|
const utils_1 = require("../../update/utils");
|
|
11
14
|
const json_1 = require("../../utils/json");
|
|
12
15
|
class UpdateView extends EasCommand_1.default {
|
|
@@ -18,25 +21,67 @@ class UpdateView extends EasCommand_1.default {
|
|
|
18
21
|
}),
|
|
19
22
|
};
|
|
20
23
|
static flags = {
|
|
24
|
+
insights: core_1.Flags.boolean({
|
|
25
|
+
description: 'Also show insights (launches, crash rate, unique users, payload size) for the update group.',
|
|
26
|
+
default: false,
|
|
27
|
+
}),
|
|
28
|
+
days: core_1.Flags.integer({
|
|
29
|
+
description: 'Show insights from the last N days (default 7). Only used with --insights.',
|
|
30
|
+
min: 1,
|
|
31
|
+
exclusive: ['start', 'end'],
|
|
32
|
+
}),
|
|
33
|
+
start: core_1.Flags.string({
|
|
34
|
+
description: 'Start of insights time range (ISO date). Only used with --insights.',
|
|
35
|
+
exclusive: ['days'],
|
|
36
|
+
}),
|
|
37
|
+
end: core_1.Flags.string({
|
|
38
|
+
description: 'End of insights time range (ISO date). Only used with --insights.',
|
|
39
|
+
exclusive: ['days'],
|
|
40
|
+
}),
|
|
21
41
|
...flags_1.EasJsonOnlyFlag,
|
|
22
42
|
};
|
|
23
43
|
static contextDefinition = {
|
|
24
44
|
...this.ContextOptions.LoggedIn,
|
|
25
45
|
};
|
|
26
46
|
async runAsync() {
|
|
27
|
-
const { args: { groupId }, flags: { json: jsonFlag }, } = await this.parse(UpdateView);
|
|
47
|
+
const { args: { groupId }, flags: { json: jsonFlag, insights: insightsFlag, days, start, end }, } = await this.parse(UpdateView);
|
|
48
|
+
if (!insightsFlag && (days !== undefined || start !== undefined || end !== undefined)) {
|
|
49
|
+
throw new Error('--days, --start, and --end can only be used with --insights.');
|
|
50
|
+
}
|
|
28
51
|
const { loggedIn: { graphqlClient }, } = await this.getContextAsync(UpdateView, { nonInteractive: true });
|
|
29
52
|
if (jsonFlag) {
|
|
30
53
|
(0, json_1.enableJsonOutput)();
|
|
31
54
|
}
|
|
32
55
|
const updatesByGroup = await UpdateQuery_1.UpdateQuery.viewUpdateGroupAsync(graphqlClient, { groupId });
|
|
56
|
+
let insightsSummary = null;
|
|
57
|
+
if (insightsFlag) {
|
|
58
|
+
const { daysBack, startTime, endTime } = (0, timeRange_1.resolveInsightsTimeRange)({ days, start, end });
|
|
59
|
+
const updatesWithInsights = await UpdateInsightsQuery_1.UpdateInsightsQuery.viewUpdateGroupInsightsAsync(graphqlClient, { groupId, startTime, endTime });
|
|
60
|
+
insightsSummary = (0, formatInsights_1.toUpdateInsightsSummary)(groupId, updatesWithInsights, {
|
|
61
|
+
startTime,
|
|
62
|
+
endTime,
|
|
63
|
+
daysBack,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
33
66
|
if (jsonFlag) {
|
|
34
|
-
(
|
|
67
|
+
if (insightsSummary) {
|
|
68
|
+
(0, json_1.printJsonOnlyOutput)({
|
|
69
|
+
updates: (0, utils_1.getUpdateJsonInfosForUpdates)(updatesByGroup),
|
|
70
|
+
insights: (0, formatInsights_1.buildUpdateInsightsJson)(insightsSummary),
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
(0, json_1.printJsonOnlyOutput)((0, utils_1.getUpdateJsonInfosForUpdates)(updatesByGroup));
|
|
75
|
+
}
|
|
35
76
|
}
|
|
36
77
|
else {
|
|
37
78
|
const [updateGroupDescription] = (0, utils_1.getUpdateGroupDescriptions)([updatesByGroup]);
|
|
38
79
|
log_1.default.log(chalk_1.default.bold('Update group:'));
|
|
39
80
|
log_1.default.log((0, utils_1.formatUpdateGroup)(updateGroupDescription));
|
|
81
|
+
if (insightsSummary) {
|
|
82
|
+
log_1.default.addNewLineIfNone();
|
|
83
|
+
log_1.default.log((0, formatInsights_1.buildUpdateInsightsTable)(insightsSummary));
|
|
84
|
+
}
|
|
40
85
|
}
|
|
41
86
|
}
|
|
42
87
|
}
|
|
@@ -34,6 +34,19 @@ async function syncCapabilityIdentifiersForEntitlementsAsync(bundleId, entitleme
|
|
|
34
34
|
// these are only APPLE_PAY, ICLOUD, APP_GROUPS
|
|
35
35
|
const CapabilityIdMapping = capabilityList_1.CapabilityMapping.filter(capability => capability.capabilityIdModel);
|
|
36
36
|
const updateRequest = [];
|
|
37
|
+
// Fetch the bundle with its linked capability identifiers to check for already linked identifiers.
|
|
38
|
+
const bundleWithRelationships = await apple_utils_1.BundleId.infoAsync(bundleId.context, {
|
|
39
|
+
id: bundleId.id,
|
|
40
|
+
query: {
|
|
41
|
+
includes: [
|
|
42
|
+
'bundleIdCapabilities',
|
|
43
|
+
'bundleIdCapabilities.appGroups',
|
|
44
|
+
'bundleIdCapabilities.merchantIds',
|
|
45
|
+
'bundleIdCapabilities.cloudContainers',
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
const linkedBundleCapabilities = bundleWithRelationships.attributes.bundleIdCapabilities ?? [];
|
|
37
50
|
// Iterate through the supported capabilities to build the request.
|
|
38
51
|
for (const classifier of CapabilityIdMapping) {
|
|
39
52
|
const CapabilityModel = classifier.capabilityIdModel;
|
|
@@ -57,12 +70,24 @@ async function syncCapabilityIdentifiersForEntitlementsAsync(bundleId, entitleme
|
|
|
57
70
|
const capabilityIds = [...new Set(entitlementValue)];
|
|
58
71
|
// Get a list of all of the capability IDs that are already created on the server.
|
|
59
72
|
const existingIds = await CapabilityModel.getAsync(bundleId.context);
|
|
73
|
+
// Opaque ids of identifiers already linked to this capability.
|
|
74
|
+
const remoteLinkedIds = linkedBundleCapabilities.find(c => c.isType(classifier.capability))
|
|
75
|
+
?.attributes;
|
|
76
|
+
const alreadyLinkedOpaqueIds = new Set((remoteLinkedIds?.[CapabilityModel.type] ?? []).map(model => model.id));
|
|
60
77
|
// A list of server IDs for linking.
|
|
61
78
|
const capabilityIdOpaqueIds = [];
|
|
62
|
-
const capabilitiesWithoutRemoteModels = capabilityIds.filter(localId => existingIds.find(model => model.attributes.identifier === localId) === undefined);
|
|
63
79
|
// Iterate through all the local IDs and see if they exist on the server.
|
|
64
|
-
for (const localId of
|
|
65
|
-
let remoteIdModel =
|
|
80
|
+
for (const localId of capabilityIds) {
|
|
81
|
+
let remoteIdModel = existingIds.find(model => model.attributes.identifier === localId);
|
|
82
|
+
// Identifier already exists.
|
|
83
|
+
if (remoteIdModel) {
|
|
84
|
+
if (!alreadyLinkedOpaqueIds.has(remoteIdModel.id)) {
|
|
85
|
+
// Link the existing identifier to this bundle.
|
|
86
|
+
linkedIds.push(remoteIdModel.attributes.identifier);
|
|
87
|
+
capabilityIdOpaqueIds.push(remoteIdModel.id);
|
|
88
|
+
}
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
66
91
|
if (log_1.default.isDebug) {
|
|
67
92
|
log_1.default.log(`Creating capability ID: ${localId} (${CapabilityModel.type})`);
|
|
68
93
|
}
|
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
import { CombinedError as GraphqlError, OperationResult } from '@urql/core';
|
|
2
|
+
export declare const EAS_CLI_UPGRADE_REQUIRED_ERROR_CODE = "EAS_CLI_UPGRADE_REQUIRED";
|
|
2
3
|
export declare function withErrorHandlingAsync<T>(promise: Promise<OperationResult<T>>): Promise<T>;
|
|
4
|
+
/**
|
|
5
|
+
* Wraps `withErrorHandlingAsync` for queries that hit endpoints which may evolve in
|
|
6
|
+
* ways that require a newer eas-cli. The server signals this by returning a GraphQL
|
|
7
|
+
* error with `extensions.errorCode === EAS_CLI_UPGRADE_REQUIRED`. As a fallback we
|
|
8
|
+
* also detect schema validation errors of the form `Cannot query field "X" on type "Y"`,
|
|
9
|
+
* which surface when a field has been removed without a coded error.
|
|
10
|
+
*
|
|
11
|
+
* In either case we re-throw an `EasCommandError` instructing the user to upgrade.
|
|
12
|
+
*/
|
|
13
|
+
export declare function withUpgradeRequiredErrorHandlingAsync<T>(promise: Promise<OperationResult<T>>, { featureName }: {
|
|
14
|
+
featureName: string;
|
|
15
|
+
}): Promise<T>;
|
|
3
16
|
export { GraphqlError };
|