eas-cli 18.4.0 → 18.6.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 +92 -90
- package/build/build/android/prepareJob.js +2 -2
- package/build/build/ios/prepareJob.js +2 -2
- package/build/build/metadata.js +2 -1
- package/build/commandUtils/EasCommand.js +23 -2
- package/build/commandUtils/context/contextUtils/getProjectIdAsync.js +2 -0
- package/build/commandUtils/pagination.d.ts +2 -1
- package/build/commandUtils/pagination.js +3 -2
- package/build/commandUtils/workflow/fetchLogs.js +11 -2
- package/build/commandUtils/workflow/types.d.ts +5 -1
- package/build/commandUtils/workflow/utils.js +22 -16
- package/build/commands/deploy/index.js +18 -2
- package/build/commands/metadata/pull.d.ts +1 -0
- package/build/commands/metadata/pull.js +9 -4
- package/build/commands/metadata/push.d.ts +1 -0
- package/build/commands/metadata/push.js +9 -4
- package/build/commands/observe/events.d.ts +27 -0
- package/build/commands/observe/events.js +140 -0
- package/build/commands/observe/metrics.d.ts +21 -0
- package/build/commands/observe/metrics.js +111 -0
- package/build/commands/observe/versions.d.ts +19 -0
- package/build/commands/observe/versions.js +69 -0
- package/build/commands/project/onboarding.js +3 -0
- package/build/commands/workflow/logs.js +12 -12
- package/build/credentials/ios/IosCredentialsProvider.js +8 -4
- package/build/credentials/ios/utils/provisioningProfile.d.ts +1 -0
- package/build/credentials/ios/utils/provisioningProfile.js +15 -1
- package/build/graphql/generated.d.ts +1273 -73
- package/build/graphql/generated.js +59 -19
- package/build/graphql/queries/ObserveQuery.d.ts +35 -0
- package/build/graphql/queries/ObserveQuery.js +109 -0
- package/build/graphql/queries/UserQuery.js +3 -0
- package/build/graphql/types/Observe.d.ts +3 -0
- package/build/graphql/types/Observe.js +84 -0
- package/build/graphql/types/Update.js +3 -0
- package/build/metadata/apple/config/reader.d.ts +13 -1
- package/build/metadata/apple/config/reader.js +33 -0
- package/build/metadata/apple/config/writer.d.ts +11 -1
- package/build/metadata/apple/config/writer.js +57 -0
- package/build/metadata/apple/data.d.ts +7 -2
- package/build/metadata/apple/rules/infoRestrictedWords.js +6 -1
- package/build/metadata/apple/tasks/age-rating.d.ts +1 -1
- package/build/metadata/apple/tasks/age-rating.js +19 -3
- package/build/metadata/apple/tasks/app-clip.d.ts +37 -0
- package/build/metadata/apple/tasks/app-clip.js +404 -0
- package/build/metadata/apple/tasks/app-review-detail.js +7 -2
- package/build/metadata/apple/tasks/index.js +6 -0
- package/build/metadata/apple/tasks/previews.d.ts +18 -0
- package/build/metadata/apple/tasks/previews.js +212 -0
- package/build/metadata/apple/tasks/screenshots.d.ts +18 -0
- package/build/metadata/apple/tasks/screenshots.js +235 -0
- package/build/metadata/apple/types.d.ts +61 -1
- package/build/metadata/auth.d.ts +11 -1
- package/build/metadata/auth.js +96 -2
- package/build/metadata/download.d.ts +5 -1
- package/build/metadata/download.js +16 -8
- package/build/metadata/upload.d.ts +5 -1
- package/build/metadata/upload.js +11 -4
- package/build/observe/fetchEvents.d.ts +27 -0
- package/build/observe/fetchEvents.js +83 -0
- package/build/observe/fetchMetrics.d.ts +11 -0
- package/build/observe/fetchMetrics.js +78 -0
- package/build/observe/fetchVersions.d.ts +7 -0
- package/build/observe/fetchVersions.js +31 -0
- package/build/observe/formatEvents.d.ts +31 -0
- package/build/observe/formatEvents.js +99 -0
- package/build/observe/formatMetrics.d.ts +38 -0
- package/build/observe/formatMetrics.js +206 -0
- package/build/observe/formatVersions.d.ts +32 -0
- package/build/observe/formatVersions.js +92 -0
- package/build/observe/metricNames.d.ts +4 -0
- package/build/observe/metricNames.js +33 -0
- package/build/observe/startAndEndTime.d.ts +18 -0
- package/build/observe/startAndEndTime.js +36 -0
- package/build/project/projectUtils.d.ts +0 -2
- package/build/project/projectUtils.js +0 -12
- package/build/project/workflow.js +1 -1
- package/build/sentry.d.ts +2 -0
- package/build/sentry.js +22 -0
- package/build/update/utils.d.ts +2 -2
- package/build/update/utils.js +1 -0
- package/build/user/SessionManager.js +11 -0
- package/build/user/User.d.ts +2 -2
- package/build/user/User.js +3 -0
- package/build/user/expoBrowserAuthFlowLauncher.js +70 -13
- package/build/worker/upload.js +15 -5
- package/oclif.manifest.json +1794 -1306
- package/package.json +15 -11
- package/schema/metadata-0.json +213 -0
package/build/metadata/auth.js
CHANGED
|
@@ -4,8 +4,14 @@ exports.getAppStoreAuthAsync = getAppStoreAuthAsync;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const apple_utils_1 = require("@expo/apple-utils");
|
|
6
6
|
const assert_1 = tslib_1.__importDefault(require("assert"));
|
|
7
|
+
const fs_1 = tslib_1.__importDefault(require("fs"));
|
|
7
8
|
const authenticate_1 = require("../credentials/ios/appstore/authenticate");
|
|
9
|
+
const authenticateTypes_1 = require("../credentials/ios/appstore/authenticateTypes");
|
|
10
|
+
const resolveCredentials_1 = require("../credentials/ios/appstore/resolveCredentials");
|
|
11
|
+
const AppStoreConnectApiKeyQuery_1 = require("../graphql/queries/AppStoreConnectApiKeyQuery");
|
|
12
|
+
const log_1 = tslib_1.__importDefault(require("../log"));
|
|
8
13
|
const bundleIdentifier_1 = require("../project/ios/bundleIdentifier");
|
|
14
|
+
const projectUtils_1 = require("../project/projectUtils");
|
|
9
15
|
/**
|
|
10
16
|
* Resolve the bundle identifier from the selected submit profile.
|
|
11
17
|
* This bundle identifier is used as target for the metadata submission.
|
|
@@ -16,15 +22,103 @@ async function resolveAppStoreBundleIdentifierAsync(projectDir, profile, exp, vc
|
|
|
16
22
|
}
|
|
17
23
|
return await (0, bundleIdentifier_1.getBundleIdentifierAsync)(projectDir, exp, vcsClient);
|
|
18
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* Try to resolve an ASC API key from the submit profile or EAS credentials service.
|
|
27
|
+
* Returns null if no key is available from these sources.
|
|
28
|
+
*/
|
|
29
|
+
async function tryResolveAscApiKeyAsync({ profile, graphqlClient, projectId, exp, bundleId, }) {
|
|
30
|
+
// 1. Check submit profile for ASC API key fields
|
|
31
|
+
if ('ascApiKeyPath' in profile && 'ascApiKeyIssuerId' in profile && 'ascApiKeyId' in profile) {
|
|
32
|
+
const { ascApiKeyPath, ascApiKeyIssuerId, ascApiKeyId } = profile;
|
|
33
|
+
if (ascApiKeyPath && ascApiKeyIssuerId && ascApiKeyId) {
|
|
34
|
+
const keyP8 = await fs_1.default.promises.readFile(ascApiKeyPath, 'utf-8');
|
|
35
|
+
// Also try to get teamId from the profile if available
|
|
36
|
+
const teamId = 'appleTeamId' in profile ? profile.appleTeamId : undefined;
|
|
37
|
+
return {
|
|
38
|
+
ascApiKey: { keyP8, keyId: ascApiKeyId, issuerId: ascApiKeyIssuerId },
|
|
39
|
+
teamId,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// 2. Look up stored credentials via EAS credentials service
|
|
44
|
+
try {
|
|
45
|
+
const account = await (0, projectUtils_1.getOwnerAccountForProjectIdAsync)(graphqlClient, projectId);
|
|
46
|
+
const appLookupParams = {
|
|
47
|
+
account,
|
|
48
|
+
projectName: exp.slug,
|
|
49
|
+
bundleIdentifier: bundleId,
|
|
50
|
+
};
|
|
51
|
+
// Import dynamically to avoid circular dependency issues
|
|
52
|
+
const { getAscApiKeyForAppSubmissionsAsync } = await Promise.resolve().then(() => tslib_1.__importStar(require('../credentials/ios/api/GraphqlClient')));
|
|
53
|
+
const ascKeyFragment = await getAscApiKeyForAppSubmissionsAsync(graphqlClient, appLookupParams);
|
|
54
|
+
if (ascKeyFragment) {
|
|
55
|
+
log_1.default.log('Using App Store Connect API Key from EAS credentials service.');
|
|
56
|
+
const fullKey = await AppStoreConnectApiKeyQuery_1.AppStoreConnectApiKeyQuery.getByIdAsync(graphqlClient, ascKeyFragment.id);
|
|
57
|
+
return {
|
|
58
|
+
ascApiKey: {
|
|
59
|
+
keyP8: fullKey.keyP8,
|
|
60
|
+
keyId: fullKey.keyIdentifier,
|
|
61
|
+
issuerId: fullKey.issuerIdentifier,
|
|
62
|
+
},
|
|
63
|
+
teamId: ascKeyFragment.appleTeam?.appleTeamIdentifier,
|
|
64
|
+
teamName: ascKeyFragment.appleTeam?.appleTeamName ?? undefined,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
// If we can't look up credentials, that's fine — we'll fall back
|
|
70
|
+
log_1.default.warn(`Could not look up stored ASC API key: ${error.message}`);
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
19
74
|
/**
|
|
20
75
|
* To start syncing ASC entities, we need access to the apple utils App instance.
|
|
21
76
|
* This resolves both the authentication and that App instance.
|
|
77
|
+
*
|
|
78
|
+
* Resolution order for authentication:
|
|
79
|
+
* 1. ASC API key from environment variables (EXPO_ASC_API_KEY_PATH, etc.)
|
|
80
|
+
* 2. ASC API key from submit profile (ascApiKeyPath, etc. in eas.json)
|
|
81
|
+
* 3. ASC API key from EAS credentials service
|
|
82
|
+
* 4. Interactive cookie auth (only when not in non-interactive mode)
|
|
22
83
|
*/
|
|
23
|
-
async function getAppStoreAuthAsync({ projectDir, profile, exp, credentialsCtx, }) {
|
|
84
|
+
async function getAppStoreAuthAsync({ projectDir, profile, exp, credentialsCtx, nonInteractive, graphqlClient, projectId, }) {
|
|
24
85
|
const bundleId = await resolveAppStoreBundleIdentifierAsync(projectDir, profile, exp, credentialsCtx.vcsClient);
|
|
86
|
+
// Try to resolve an ASC API key from profile or credentials service
|
|
87
|
+
const resolvedKey = await tryResolveAscApiKeyAsync({
|
|
88
|
+
profile,
|
|
89
|
+
graphqlClient,
|
|
90
|
+
projectId,
|
|
91
|
+
exp,
|
|
92
|
+
bundleId,
|
|
93
|
+
});
|
|
94
|
+
if (resolvedKey || (0, resolveCredentials_1.hasAscEnvVars)()) {
|
|
95
|
+
const authOptions = {
|
|
96
|
+
mode: authenticateTypes_1.AuthenticationMode.API_KEY,
|
|
97
|
+
...(resolvedKey
|
|
98
|
+
? {
|
|
99
|
+
ascApiKey: resolvedKey.ascApiKey,
|
|
100
|
+
teamId: resolvedKey.teamId,
|
|
101
|
+
teamName: resolvedKey.teamName,
|
|
102
|
+
// Default to COMPANY_OR_ORGANIZATION to avoid prompting for team type
|
|
103
|
+
teamType: authenticateTypes_1.AppleTeamType.COMPANY_OR_ORGANIZATION,
|
|
104
|
+
}
|
|
105
|
+
: {}),
|
|
106
|
+
};
|
|
107
|
+
const authCtx = await credentialsCtx.appStore.ensureAuthenticatedAsync(authOptions);
|
|
108
|
+
(0, assert_1.default)(authCtx.authState, 'Failed to authenticate with App Store Connect');
|
|
109
|
+
const app = await apple_utils_1.App.findAsync((0, authenticate_1.getRequestContext)(authCtx), { bundleId });
|
|
110
|
+
(0, assert_1.default)(app, `Failed to load app "${bundleId}" from App Store Connect`);
|
|
111
|
+
return { app, auth: authCtx.authState };
|
|
112
|
+
}
|
|
113
|
+
if (nonInteractive) {
|
|
114
|
+
throw new Error('No App Store Connect API Key found. In non-interactive mode, provide one via:\n' +
|
|
115
|
+
' - Environment variables: EXPO_ASC_API_KEY_PATH, EXPO_ASC_KEY_ID, EXPO_ASC_ISSUER_ID\n' +
|
|
116
|
+
' - eas.json submit profile: ascApiKeyPath, ascApiKeyId, ascApiKeyIssuerId\n' +
|
|
117
|
+
' - EAS credentials service: run `eas credentials` to set up an API key');
|
|
118
|
+
}
|
|
119
|
+
// Fall back to interactive cookie auth
|
|
25
120
|
const authCtx = await credentialsCtx.appStore.ensureAuthenticatedAsync();
|
|
26
121
|
(0, assert_1.default)(authCtx.authState, 'Failed to authenticate with App Store Connect');
|
|
27
|
-
// TODO: improve error handling by mentioning possible configuration errors
|
|
28
122
|
const app = await apple_utils_1.App.findAsync((0, authenticate_1.getRequestContext)(authCtx), { bundleId });
|
|
29
123
|
(0, assert_1.default)(app, `Failed to load app "${bundleId}" from App Store Connect`);
|
|
30
124
|
return { app, auth: authCtx.authState };
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
import { ExpoConfig } from '@expo/config';
|
|
2
2
|
import { SubmitProfile } from '@expo/eas-json';
|
|
3
3
|
import { Analytics } from '../analytics/AnalyticsManager';
|
|
4
|
+
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
4
5
|
import { CredentialsContext } from '../credentials/context';
|
|
5
6
|
/**
|
|
6
7
|
* Generate a local store configuration from the stores.
|
|
7
8
|
* Note, only App Store is supported at this time.
|
|
8
9
|
*/
|
|
9
|
-
export declare function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }: {
|
|
10
|
+
export declare function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }: {
|
|
10
11
|
projectDir: string;
|
|
11
12
|
profile: SubmitProfile;
|
|
12
13
|
exp: ExpoConfig;
|
|
13
14
|
analytics: Analytics;
|
|
14
15
|
credentialsCtx: CredentialsContext;
|
|
16
|
+
nonInteractive: boolean;
|
|
17
|
+
graphqlClient: ExpoGraphqlClient;
|
|
18
|
+
projectId: string;
|
|
15
19
|
}): Promise<string>;
|
|
@@ -16,16 +16,21 @@ const prompts_1 = require("../prompts");
|
|
|
16
16
|
* Generate a local store configuration from the stores.
|
|
17
17
|
* Note, only App Store is supported at this time.
|
|
18
18
|
*/
|
|
19
|
-
async function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }) {
|
|
19
|
+
async function downloadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }) {
|
|
20
20
|
const filePath = (0, resolve_1.getStaticConfigFilePath)({ projectDir, profile });
|
|
21
21
|
const fileExists = await fs_extra_1.default.pathExists(filePath);
|
|
22
22
|
if (fileExists) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
23
|
+
if (nonInteractive) {
|
|
24
|
+
log_1.default.log(`Overwriting existing store config at "${path_1.default.relative(projectDir, filePath)}".`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
const filePathRelative = path_1.default.relative(projectDir, filePath);
|
|
28
|
+
const overwrite = await (0, prompts_1.confirmAsync)({
|
|
29
|
+
message: `Do you want to overwrite the existing "${filePathRelative}"?`,
|
|
30
|
+
});
|
|
31
|
+
if (!overwrite) {
|
|
32
|
+
throw new errors_1.MetadataValidationError(`Store config already exists at "${filePath}"`);
|
|
33
|
+
}
|
|
29
34
|
}
|
|
30
35
|
}
|
|
31
36
|
const { app, auth } = await (0, auth_1.getAppStoreAuthAsync)({
|
|
@@ -33,6 +38,9 @@ async function downloadMetadataAsync({ projectDir, profile, exp, analytics, cred
|
|
|
33
38
|
credentialsCtx,
|
|
34
39
|
projectDir,
|
|
35
40
|
profile,
|
|
41
|
+
nonInteractive,
|
|
42
|
+
graphqlClient,
|
|
43
|
+
projectId,
|
|
36
44
|
});
|
|
37
45
|
const { unsubscribeTelemetry, executionId } = await (0, telemetry_1.subscribeTelemetryAsync)(analytics, AnalyticsManager_1.MetadataEvent.APPLE_METADATA_DOWNLOAD, { app, auth });
|
|
38
46
|
log_1.default.addNewLineIfNone();
|
|
@@ -40,7 +48,7 @@ async function downloadMetadataAsync({ projectDir, profile, exp, analytics, cred
|
|
|
40
48
|
const errors = [];
|
|
41
49
|
const config = (0, resolve_1.createAppleWriter)();
|
|
42
50
|
const tasks = (0, tasks_1.createAppleTasks)();
|
|
43
|
-
const taskCtx = { app };
|
|
51
|
+
const taskCtx = { app, projectDir };
|
|
44
52
|
for (const task of tasks) {
|
|
45
53
|
try {
|
|
46
54
|
await task.prepareAsync({ context: taskCtx });
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { ExpoConfig } from '@expo/config';
|
|
2
2
|
import { SubmitProfile } from '@expo/eas-json';
|
|
3
3
|
import { Analytics } from '../analytics/AnalyticsManager';
|
|
4
|
+
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
4
5
|
import { CredentialsContext } from '../credentials/context';
|
|
5
6
|
/**
|
|
6
7
|
* Sync a local store configuration with the stores.
|
|
7
8
|
* Note, only App Store is supported at this time.
|
|
8
9
|
*/
|
|
9
|
-
export declare function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }: {
|
|
10
|
+
export declare function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }: {
|
|
10
11
|
projectDir: string;
|
|
11
12
|
profile: SubmitProfile;
|
|
12
13
|
exp: ExpoConfig;
|
|
13
14
|
analytics: Analytics;
|
|
14
15
|
credentialsCtx: CredentialsContext;
|
|
16
|
+
nonInteractive: boolean;
|
|
17
|
+
graphqlClient: ExpoGraphqlClient;
|
|
18
|
+
projectId: string;
|
|
15
19
|
}): Promise<{
|
|
16
20
|
appleLink: string;
|
|
17
21
|
}>;
|
package/build/metadata/upload.js
CHANGED
|
@@ -14,13 +14,16 @@ const prompts_1 = require("../prompts");
|
|
|
14
14
|
* Sync a local store configuration with the stores.
|
|
15
15
|
* Note, only App Store is supported at this time.
|
|
16
16
|
*/
|
|
17
|
-
async function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, }) {
|
|
18
|
-
const storeConfig = await loadConfigWithValidationPromptAsync(projectDir, profile);
|
|
17
|
+
async function uploadMetadataAsync({ projectDir, profile, exp, analytics, credentialsCtx, nonInteractive, graphqlClient, projectId, }) {
|
|
18
|
+
const storeConfig = await loadConfigWithValidationPromptAsync(projectDir, profile, nonInteractive);
|
|
19
19
|
const { app, auth } = await (0, auth_1.getAppStoreAuthAsync)({
|
|
20
20
|
exp,
|
|
21
21
|
credentialsCtx,
|
|
22
22
|
projectDir,
|
|
23
23
|
profile,
|
|
24
|
+
nonInteractive,
|
|
25
|
+
graphqlClient,
|
|
26
|
+
projectId,
|
|
24
27
|
});
|
|
25
28
|
const { unsubscribeTelemetry, executionId } = await (0, telemetry_1.subscribeTelemetryAsync)(analytics, AnalyticsManager_1.MetadataEvent.APPLE_METADATA_UPLOAD, { app, auth });
|
|
26
29
|
log_1.default.addNewLineIfNone();
|
|
@@ -32,7 +35,7 @@ async function uploadMetadataAsync({ projectDir, profile, exp, analytics, creden
|
|
|
32
35
|
// This version is the parent model of all changes we are going to push.
|
|
33
36
|
version: config.getVersion()?.versionString,
|
|
34
37
|
});
|
|
35
|
-
const taskCtx = { app };
|
|
38
|
+
const taskCtx = { app, projectDir };
|
|
36
39
|
for (const task of tasks) {
|
|
37
40
|
try {
|
|
38
41
|
await task.prepareAsync({ context: taskCtx });
|
|
@@ -55,12 +58,16 @@ async function uploadMetadataAsync({ projectDir, profile, exp, analytics, creden
|
|
|
55
58
|
}
|
|
56
59
|
return { appleLink: `https://appstoreconnect.apple.com/apps/${app.id}/appstore` };
|
|
57
60
|
}
|
|
58
|
-
async function loadConfigWithValidationPromptAsync(projectDir, profile) {
|
|
61
|
+
async function loadConfigWithValidationPromptAsync(projectDir, profile, nonInteractive) {
|
|
59
62
|
try {
|
|
60
63
|
return await (0, resolve_1.loadConfigAsync)({ projectDir, profile });
|
|
61
64
|
}
|
|
62
65
|
catch (error) {
|
|
63
66
|
if (error instanceof errors_1.MetadataValidationError) {
|
|
67
|
+
if (nonInteractive) {
|
|
68
|
+
(0, errors_1.logMetadataValidationError)(error);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
64
71
|
(0, errors_1.logMetadataValidationError)(error);
|
|
65
72
|
log_1.default.newLine();
|
|
66
73
|
log_1.default.warn('Without further updates, the current store configuration can fail to be synchronized with the App Store or pass App Store review.');
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
2
|
+
import { AppObserveEvent, AppObserveEventsOrderBy, AppObservePlatform, AppPlatform, PageInfo } from '../graphql/generated';
|
|
3
|
+
export declare enum EventsOrderPreset {
|
|
4
|
+
Slowest = "SLOWEST",
|
|
5
|
+
Fastest = "FASTEST",
|
|
6
|
+
Newest = "NEWEST",
|
|
7
|
+
Oldest = "OLDEST"
|
|
8
|
+
}
|
|
9
|
+
export declare function resolveOrderBy(input: string): AppObserveEventsOrderBy;
|
|
10
|
+
interface FetchObserveEventsOptions {
|
|
11
|
+
metricName: string;
|
|
12
|
+
orderBy: AppObserveEventsOrderBy;
|
|
13
|
+
limit: number;
|
|
14
|
+
after?: string;
|
|
15
|
+
startTime: string;
|
|
16
|
+
endTime: string;
|
|
17
|
+
platform?: AppObservePlatform;
|
|
18
|
+
appVersion?: string;
|
|
19
|
+
updateId?: string;
|
|
20
|
+
}
|
|
21
|
+
interface FetchObserveEventsResult {
|
|
22
|
+
events: AppObserveEvent[];
|
|
23
|
+
pageInfo: PageInfo;
|
|
24
|
+
}
|
|
25
|
+
export declare function fetchObserveEventsAsync(graphqlClient: ExpoGraphqlClient, appId: string, options: FetchObserveEventsOptions): Promise<FetchObserveEventsResult>;
|
|
26
|
+
export declare function fetchTotalEventCountAsync(graphqlClient: ExpoGraphqlClient, appId: string, metricName: string, platforms: AppPlatform[], startTime: string, endTime: string): Promise<number>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.EventsOrderPreset = void 0;
|
|
4
|
+
exports.resolveOrderBy = resolveOrderBy;
|
|
5
|
+
exports.fetchObserveEventsAsync = fetchObserveEventsAsync;
|
|
6
|
+
exports.fetchTotalEventCountAsync = fetchTotalEventCountAsync;
|
|
7
|
+
const generated_1 = require("../graphql/generated");
|
|
8
|
+
const ObserveQuery_1 = require("../graphql/queries/ObserveQuery");
|
|
9
|
+
var EventsOrderPreset;
|
|
10
|
+
(function (EventsOrderPreset) {
|
|
11
|
+
EventsOrderPreset["Slowest"] = "SLOWEST";
|
|
12
|
+
EventsOrderPreset["Fastest"] = "FASTEST";
|
|
13
|
+
EventsOrderPreset["Newest"] = "NEWEST";
|
|
14
|
+
EventsOrderPreset["Oldest"] = "OLDEST";
|
|
15
|
+
})(EventsOrderPreset || (exports.EventsOrderPreset = EventsOrderPreset = {}));
|
|
16
|
+
function resolveOrderBy(input) {
|
|
17
|
+
const preset = input.toUpperCase();
|
|
18
|
+
switch (preset) {
|
|
19
|
+
case EventsOrderPreset.Slowest:
|
|
20
|
+
return {
|
|
21
|
+
field: generated_1.AppObserveEventsOrderByField.MetricValue,
|
|
22
|
+
direction: generated_1.AppObserveEventsOrderByDirection.Desc,
|
|
23
|
+
};
|
|
24
|
+
case EventsOrderPreset.Fastest:
|
|
25
|
+
return {
|
|
26
|
+
field: generated_1.AppObserveEventsOrderByField.MetricValue,
|
|
27
|
+
direction: generated_1.AppObserveEventsOrderByDirection.Asc,
|
|
28
|
+
};
|
|
29
|
+
case EventsOrderPreset.Newest:
|
|
30
|
+
return {
|
|
31
|
+
field: generated_1.AppObserveEventsOrderByField.Timestamp,
|
|
32
|
+
direction: generated_1.AppObserveEventsOrderByDirection.Desc,
|
|
33
|
+
};
|
|
34
|
+
case EventsOrderPreset.Oldest:
|
|
35
|
+
return {
|
|
36
|
+
field: generated_1.AppObserveEventsOrderByField.Timestamp,
|
|
37
|
+
direction: generated_1.AppObserveEventsOrderByDirection.Asc,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function fetchObserveEventsAsync(graphqlClient, appId, options) {
|
|
42
|
+
const filter = {
|
|
43
|
+
metricName: options.metricName,
|
|
44
|
+
startTime: options.startTime,
|
|
45
|
+
endTime: options.endTime,
|
|
46
|
+
...(options.platform && { platform: options.platform }),
|
|
47
|
+
...(options.appVersion && { appVersion: options.appVersion }),
|
|
48
|
+
...(options.updateId && { appUpdateId: options.updateId }),
|
|
49
|
+
};
|
|
50
|
+
return await ObserveQuery_1.ObserveQuery.eventsAsync(graphqlClient, {
|
|
51
|
+
appId,
|
|
52
|
+
filter,
|
|
53
|
+
first: options.limit,
|
|
54
|
+
...(options.after && { after: options.after }),
|
|
55
|
+
orderBy: options.orderBy,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const appPlatformToObservePlatform = {
|
|
59
|
+
[generated_1.AppPlatform.Android]: generated_1.AppObservePlatform.Android,
|
|
60
|
+
[generated_1.AppPlatform.Ios]: generated_1.AppObservePlatform.Ios,
|
|
61
|
+
};
|
|
62
|
+
async function fetchTotalEventCountAsync(graphqlClient, appId, metricName, platforms, startTime, endTime) {
|
|
63
|
+
const queries = platforms.map(async (appPlatform) => {
|
|
64
|
+
try {
|
|
65
|
+
const versions = await ObserveQuery_1.ObserveQuery.appVersionsAsync(graphqlClient, {
|
|
66
|
+
appId,
|
|
67
|
+
platform: appPlatformToObservePlatform[appPlatform],
|
|
68
|
+
startTime,
|
|
69
|
+
endTime,
|
|
70
|
+
metricNames: [metricName],
|
|
71
|
+
});
|
|
72
|
+
return versions.reduce((sum, v) => {
|
|
73
|
+
const metric = v.metrics.find(m => m.metricName === metricName);
|
|
74
|
+
return sum + (metric?.eventCount ?? 0);
|
|
75
|
+
}, 0);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return 0;
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
const counts = await Promise.all(queries);
|
|
82
|
+
return counts.reduce((a, b) => a + b, 0);
|
|
83
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
2
|
+
import { AppPlatform } from '../graphql/generated';
|
|
3
|
+
import { BuildNumbersMap, ObserveMetricsMap, UpdateIdsMap } from './formatMetrics';
|
|
4
|
+
export declare function validateDateFlag(value: string, flagName: string): void;
|
|
5
|
+
export interface FetchObserveMetricsResult {
|
|
6
|
+
metricsMap: ObserveMetricsMap;
|
|
7
|
+
buildNumbersMap: BuildNumbersMap;
|
|
8
|
+
updateIdsMap: UpdateIdsMap;
|
|
9
|
+
totalEventCounts: Map<string, number>;
|
|
10
|
+
}
|
|
11
|
+
export declare function fetchObserveMetricsAsync(graphqlClient: ExpoGraphqlClient, appId: string, metricNames: string[], platforms: AppPlatform[], startTime: string, endTime: string): Promise<FetchObserveMetricsResult>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateDateFlag = validateDateFlag;
|
|
4
|
+
exports.fetchObserveMetricsAsync = fetchObserveMetricsAsync;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const errors_1 = require("../commandUtils/errors");
|
|
7
|
+
const generated_1 = require("../graphql/generated");
|
|
8
|
+
const ObserveQuery_1 = require("../graphql/queries/ObserveQuery");
|
|
9
|
+
const log_1 = tslib_1.__importDefault(require("../log"));
|
|
10
|
+
const formatMetrics_1 = require("./formatMetrics");
|
|
11
|
+
const appPlatformToObservePlatform = {
|
|
12
|
+
[generated_1.AppPlatform.Android]: generated_1.AppObservePlatform.Android,
|
|
13
|
+
[generated_1.AppPlatform.Ios]: generated_1.AppObservePlatform.Ios,
|
|
14
|
+
};
|
|
15
|
+
function validateDateFlag(value, flagName) {
|
|
16
|
+
const parsed = new Date(value);
|
|
17
|
+
if (isNaN(parsed.getTime())) {
|
|
18
|
+
throw new errors_1.EasCommandError(`Invalid ${flagName} date: "${value}". Provide a valid ISO 8601 date (e.g. 2025-01-01).`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function fetchObserveMetricsAsync(graphqlClient, appId, metricNames, platforms, startTime, endTime) {
|
|
22
|
+
const queries = platforms.map(async (appPlatform) => {
|
|
23
|
+
const observePlatform = appPlatformToObservePlatform[appPlatform];
|
|
24
|
+
try {
|
|
25
|
+
const appVersions = await ObserveQuery_1.ObserveQuery.appVersionsAsync(graphqlClient, {
|
|
26
|
+
appId,
|
|
27
|
+
platform: observePlatform,
|
|
28
|
+
startTime,
|
|
29
|
+
endTime,
|
|
30
|
+
metricNames,
|
|
31
|
+
});
|
|
32
|
+
return { appPlatform, appVersions };
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
log_1.default.warn(`Failed to fetch observe data on ${observePlatform}: ${error.message}`);
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const results = await Promise.all(queries);
|
|
40
|
+
const metricsMap = new Map();
|
|
41
|
+
const buildNumbersMap = new Map();
|
|
42
|
+
const updateIdsMap = new Map();
|
|
43
|
+
const totalEventCounts = new Map();
|
|
44
|
+
for (const result of results) {
|
|
45
|
+
if (!result) {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
const { appPlatform, appVersions } = result;
|
|
49
|
+
for (const version of appVersions) {
|
|
50
|
+
const key = (0, formatMetrics_1.makeMetricsKey)(version.appVersion, appPlatform);
|
|
51
|
+
if (!metricsMap.has(key)) {
|
|
52
|
+
metricsMap.set(key, new Map());
|
|
53
|
+
}
|
|
54
|
+
if (!buildNumbersMap.has(key)) {
|
|
55
|
+
buildNumbersMap.set(key, version.buildNumbers.map(bn => bn.appBuildNumber));
|
|
56
|
+
}
|
|
57
|
+
if (!updateIdsMap.has(key)) {
|
|
58
|
+
updateIdsMap.set(key, version.updates.map(u => u.appUpdateId));
|
|
59
|
+
}
|
|
60
|
+
for (const metric of version.metrics) {
|
|
61
|
+
const values = {
|
|
62
|
+
min: metric.statistics.min,
|
|
63
|
+
max: metric.statistics.max,
|
|
64
|
+
median: metric.statistics.median,
|
|
65
|
+
average: metric.statistics.average,
|
|
66
|
+
p80: metric.statistics.p80,
|
|
67
|
+
p90: metric.statistics.p90,
|
|
68
|
+
p99: metric.statistics.p99,
|
|
69
|
+
eventCount: metric.eventCount,
|
|
70
|
+
};
|
|
71
|
+
metricsMap.get(key).set(metric.metricName, values);
|
|
72
|
+
const eventCountKey = `${metric.metricName}:${appPlatform}`;
|
|
73
|
+
totalEventCounts.set(eventCountKey, (totalEventCounts.get(eventCountKey) ?? 0) + metric.eventCount);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return { metricsMap, buildNumbersMap, updateIdsMap, totalEventCounts };
|
|
78
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ExpoGraphqlClient } from '../commandUtils/context/contextUtils/createGraphqlClient';
|
|
2
|
+
import { AppObserveAppVersion, AppPlatform } from '../graphql/generated';
|
|
3
|
+
export interface AppVersionsResult {
|
|
4
|
+
platform: AppPlatform;
|
|
5
|
+
appVersions: AppObserveAppVersion[];
|
|
6
|
+
}
|
|
7
|
+
export declare function fetchObserveVersionsAsync(graphqlClient: ExpoGraphqlClient, appId: string, platforms: AppPlatform[], startTime: string, endTime: string): Promise<AppVersionsResult[]>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchObserveVersionsAsync = fetchObserveVersionsAsync;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const generated_1 = require("../graphql/generated");
|
|
6
|
+
const ObserveQuery_1 = require("../graphql/queries/ObserveQuery");
|
|
7
|
+
const log_1 = tslib_1.__importDefault(require("../log"));
|
|
8
|
+
const appPlatformToObservePlatform = {
|
|
9
|
+
[generated_1.AppPlatform.Android]: generated_1.AppObservePlatform.Android,
|
|
10
|
+
[generated_1.AppPlatform.Ios]: generated_1.AppObservePlatform.Ios,
|
|
11
|
+
};
|
|
12
|
+
async function fetchObserveVersionsAsync(graphqlClient, appId, platforms, startTime, endTime) {
|
|
13
|
+
const queries = platforms.map(async (appPlatform) => {
|
|
14
|
+
const observePlatform = appPlatformToObservePlatform[appPlatform];
|
|
15
|
+
try {
|
|
16
|
+
const appVersions = await ObserveQuery_1.ObserveQuery.appVersionsAsync(graphqlClient, {
|
|
17
|
+
appId,
|
|
18
|
+
platform: observePlatform,
|
|
19
|
+
startTime,
|
|
20
|
+
endTime,
|
|
21
|
+
});
|
|
22
|
+
return { platform: appPlatform, appVersions };
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
log_1.default.warn(`Failed to fetch app versions for ${observePlatform}: ${error.message}`);
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
const results = await Promise.all(queries);
|
|
30
|
+
return results.filter((r) => r !== null);
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { AppObserveEvent, PageInfo } from '../graphql/generated';
|
|
2
|
+
export interface ObserveEventJson {
|
|
3
|
+
id: string;
|
|
4
|
+
metricName: string;
|
|
5
|
+
metricValue: number;
|
|
6
|
+
appVersion: string;
|
|
7
|
+
appBuildNumber: string;
|
|
8
|
+
appUpdateId: string | null;
|
|
9
|
+
deviceModel: string;
|
|
10
|
+
deviceOs: string;
|
|
11
|
+
deviceOsVersion: string;
|
|
12
|
+
countryCode: string | null;
|
|
13
|
+
sessionId: string | null;
|
|
14
|
+
easClientId: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
}
|
|
17
|
+
export interface BuildEventsTableOptions {
|
|
18
|
+
metricName: string;
|
|
19
|
+
daysBack?: number;
|
|
20
|
+
startTime?: string;
|
|
21
|
+
endTime?: string;
|
|
22
|
+
totalEventCount?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function buildObserveEventsTable(events: AppObserveEvent[], pageInfo: PageInfo, options?: BuildEventsTableOptions): string;
|
|
25
|
+
export declare function buildObserveEventsJson(events: AppObserveEvent[], pageInfo: PageInfo): {
|
|
26
|
+
events: ObserveEventJson[];
|
|
27
|
+
pageInfo: {
|
|
28
|
+
hasNextPage: boolean;
|
|
29
|
+
endCursor: string | null;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildObserveEventsTable = buildObserveEventsTable;
|
|
4
|
+
exports.buildObserveEventsJson = buildObserveEventsJson;
|
|
5
|
+
const tslib_1 = require("tslib");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const metricNames_1 = require("./metricNames");
|
|
8
|
+
function formatTimestamp(isoString) {
|
|
9
|
+
const date = new Date(isoString);
|
|
10
|
+
return date.toLocaleDateString('en-US', {
|
|
11
|
+
year: 'numeric',
|
|
12
|
+
month: 'short',
|
|
13
|
+
day: 'numeric',
|
|
14
|
+
hour: '2-digit',
|
|
15
|
+
minute: '2-digit',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
function formatDate(isoString) {
|
|
19
|
+
const date = new Date(isoString);
|
|
20
|
+
return date.toLocaleDateString('en-US', {
|
|
21
|
+
year: 'numeric',
|
|
22
|
+
month: 'short',
|
|
23
|
+
day: 'numeric',
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function buildObserveEventsTable(events, pageInfo, options) {
|
|
27
|
+
if (events.length === 0) {
|
|
28
|
+
return chalk_1.default.yellow('No events found.');
|
|
29
|
+
}
|
|
30
|
+
const hasUpdates = events.some(e => e.appUpdateId);
|
|
31
|
+
const headers = [
|
|
32
|
+
'Value',
|
|
33
|
+
'App Version',
|
|
34
|
+
...(hasUpdates ? ['Update'] : []),
|
|
35
|
+
'Platform',
|
|
36
|
+
'Device',
|
|
37
|
+
'Country',
|
|
38
|
+
'Timestamp',
|
|
39
|
+
];
|
|
40
|
+
const rows = events.map(event => [
|
|
41
|
+
`${event.metricValue.toFixed(2)}s`,
|
|
42
|
+
`${event.appVersion} (${event.appBuildNumber})`,
|
|
43
|
+
...(hasUpdates ? [event.appUpdateId ?? '-'] : []),
|
|
44
|
+
`${event.deviceOs} ${event.deviceOsVersion}`,
|
|
45
|
+
event.deviceModel,
|
|
46
|
+
event.countryCode ?? '-',
|
|
47
|
+
formatTimestamp(event.timestamp),
|
|
48
|
+
]);
|
|
49
|
+
const colWidths = headers.map((h, i) => Math.max(h.length, ...rows.map(r => r[i].length)));
|
|
50
|
+
const headerLine = headers.map((h, i) => h.padEnd(colWidths[i])).join(' ');
|
|
51
|
+
const separatorLine = colWidths.map(w => '-'.repeat(w)).join(' ');
|
|
52
|
+
const dataLines = rows.map(row => row.map((cell, i) => cell.padEnd(colWidths[i])).join(' '));
|
|
53
|
+
const lines = [];
|
|
54
|
+
if (options) {
|
|
55
|
+
const metricDisplay = (0, metricNames_1.getMetricDisplayName)(options.metricName);
|
|
56
|
+
let timeDesc;
|
|
57
|
+
if (options.daysBack) {
|
|
58
|
+
timeDesc = `for the last ${options.daysBack} days`;
|
|
59
|
+
}
|
|
60
|
+
else if (options.startTime && options.endTime) {
|
|
61
|
+
timeDesc = `from ${formatDate(options.startTime)} to ${formatDate(options.endTime)}`;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
timeDesc = '';
|
|
65
|
+
}
|
|
66
|
+
const totalDesc = options.totalEventCount != null
|
|
67
|
+
? ` — ${options.totalEventCount.toLocaleString()} total events`
|
|
68
|
+
: '';
|
|
69
|
+
lines.push(chalk_1.default.bold(`${metricDisplay} events ${timeDesc}${totalDesc}`.trim()), '');
|
|
70
|
+
}
|
|
71
|
+
lines.push(chalk_1.default.bold(headerLine), separatorLine, ...dataLines);
|
|
72
|
+
if (pageInfo.hasNextPage && pageInfo.endCursor) {
|
|
73
|
+
lines.push('', `Next page: --after ${pageInfo.endCursor}`);
|
|
74
|
+
}
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
77
|
+
function buildObserveEventsJson(events, pageInfo) {
|
|
78
|
+
return {
|
|
79
|
+
events: events.map(event => ({
|
|
80
|
+
id: event.id,
|
|
81
|
+
metricName: event.metricName,
|
|
82
|
+
metricValue: event.metricValue,
|
|
83
|
+
appVersion: event.appVersion,
|
|
84
|
+
appBuildNumber: event.appBuildNumber,
|
|
85
|
+
appUpdateId: event.appUpdateId ?? null,
|
|
86
|
+
deviceModel: event.deviceModel,
|
|
87
|
+
deviceOs: event.deviceOs,
|
|
88
|
+
deviceOsVersion: event.deviceOsVersion,
|
|
89
|
+
countryCode: event.countryCode ?? null,
|
|
90
|
+
sessionId: event.sessionId ?? null,
|
|
91
|
+
easClientId: event.easClientId,
|
|
92
|
+
timestamp: event.timestamp,
|
|
93
|
+
})),
|
|
94
|
+
pageInfo: {
|
|
95
|
+
hasNextPage: pageInfo.hasNextPage,
|
|
96
|
+
endCursor: pageInfo.endCursor ?? null,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|