eas-cli 20.2.0 → 20.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -110
- package/build/commandUtils/new/templates/AGENTS.md +25 -146
- package/build/commandUtils/new/templates/CLAUDE.md +1 -9
- package/build/commandUtils/posthog.d.ts +4 -0
- package/build/commandUtils/posthog.js +23 -0
- package/build/commands/account/audit.d.ts +17 -0
- package/build/commands/account/audit.js +112 -0
- package/build/commands/integrations/posthog/connect.d.ts +27 -0
- package/build/commands/integrations/posthog/connect.js +432 -0
- package/build/commands/integrations/posthog/dashboard.d.ts +13 -0
- package/build/commands/integrations/posthog/dashboard.js +66 -0
- package/build/commands/integrations/posthog/disconnect.d.ts +14 -0
- package/build/commands/integrations/posthog/disconnect.js +80 -0
- package/build/commands/observe/session.d.ts +18 -0
- package/build/commands/observe/session.js +65 -0
- package/build/commands/update/view.d.ts +7 -0
- package/build/commands/update/view.js +30 -3
- package/build/graphql/generated.d.ts +468 -2
- package/build/graphql/generated.js +28 -4
- package/build/graphql/mutations/PostHogMutation.d.ts +8 -0
- package/build/graphql/mutations/PostHogMutation.js +55 -0
- package/build/graphql/queries/AuditLogQuery.d.ts +6 -0
- package/build/graphql/queries/AuditLogQuery.js +57 -0
- package/build/graphql/queries/DeviceRunSessionQuery.js +1 -0
- package/build/graphql/queries/PostHogQuery.d.ts +6 -0
- package/build/graphql/queries/PostHogQuery.js +49 -0
- package/build/graphql/types/AuditLog.d.ts +1 -0
- package/build/graphql/types/AuditLog.js +18 -0
- package/build/graphql/types/Observe.js +1 -0
- package/build/graphql/types/PostHogConnection.d.ts +7 -0
- package/build/graphql/types/PostHogConnection.js +30 -0
- package/build/observe/fetchCustomEvents.d.ts +2 -2
- package/build/observe/fetchCustomEvents.js +2 -2
- package/build/observe/fetchEvents.d.ts +4 -3
- package/build/observe/fetchEvents.js +4 -3
- package/build/observe/fetchSessions.d.ts +51 -0
- package/build/observe/fetchSessions.js +86 -0
- package/build/observe/formatEvents.d.ts +1 -0
- package/build/observe/formatEvents.js +1 -0
- package/build/observe/formatSessions.d.ts +15 -0
- package/build/observe/formatSessions.js +100 -0
- package/build/simulator/utils.js +28 -5
- package/build/update/getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync.js +23 -26
- package/build/user/SessionManager.d.ts +1 -22
- package/build/user/SessionManager.js +7 -89
- package/oclif.manifest.json +1583 -1108
- package/package.json +7 -3
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const spawn_async_1 = tslib_1.__importDefault(require("@expo/spawn-async"));
|
|
5
|
+
const core_1 = require("@oclif/core");
|
|
6
|
+
const chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
7
|
+
const dotenv_1 = tslib_1.__importDefault(require("dotenv"));
|
|
8
|
+
const fs = tslib_1.__importStar(require("fs-extra"));
|
|
9
|
+
const path_1 = tslib_1.__importDefault(require("path"));
|
|
10
|
+
const environment_1 = require("../../../build/utils/environment");
|
|
11
|
+
const EasCommand_1 = tslib_1.__importDefault(require("../../../commandUtils/EasCommand"));
|
|
12
|
+
const flags_1 = require("../../../commandUtils/flags");
|
|
13
|
+
const posthog_1 = require("../../../commandUtils/posthog");
|
|
14
|
+
const generated_1 = require("../../../graphql/generated");
|
|
15
|
+
const EnvironmentVariableMutation_1 = require("../../../graphql/mutations/EnvironmentVariableMutation");
|
|
16
|
+
const PostHogMutation_1 = require("../../../graphql/mutations/PostHogMutation");
|
|
17
|
+
const EnvironmentVariablesQuery_1 = require("../../../graphql/queries/EnvironmentVariablesQuery");
|
|
18
|
+
const PostHogQuery_1 = require("../../../graphql/queries/PostHogQuery");
|
|
19
|
+
const log_1 = tslib_1.__importStar(require("../../../log"));
|
|
20
|
+
const ora_1 = require("../../../ora");
|
|
21
|
+
const expoConfig_1 = require("../../../project/expoConfig");
|
|
22
|
+
const projectUtils_1 = require("../../../project/projectUtils");
|
|
23
|
+
const prompts_1 = require("../../../prompts");
|
|
24
|
+
const json_1 = require("../../../utils/json");
|
|
25
|
+
const POSTHOG_REGIONS = [
|
|
26
|
+
{ title: 'United States (US)', value: generated_1.PostHogRegion.Us },
|
|
27
|
+
{ title: 'European Union (EU)', value: generated_1.PostHogRegion.Eu },
|
|
28
|
+
];
|
|
29
|
+
const EAS_POSTHOG_ENVIRONMENTS = [
|
|
30
|
+
environment_1.DefaultEnvironment.Production,
|
|
31
|
+
environment_1.DefaultEnvironment.Preview,
|
|
32
|
+
environment_1.DefaultEnvironment.Development,
|
|
33
|
+
];
|
|
34
|
+
const SDK_PACKAGES = [
|
|
35
|
+
'posthog-react-native',
|
|
36
|
+
'expo-file-system',
|
|
37
|
+
'expo-application',
|
|
38
|
+
'expo-device',
|
|
39
|
+
'expo-localization',
|
|
40
|
+
];
|
|
41
|
+
const SESSION_REPLAY_PACKAGE = 'posthog-react-native-session-replay';
|
|
42
|
+
const CONFIG_PLUGIN = 'posthog-react-native/expo';
|
|
43
|
+
const EAS_POSTHOG_API_KEY_ENV_VAR_NAME = 'EXPO_PUBLIC_POSTHOG_API_KEY';
|
|
44
|
+
const EAS_POSTHOG_HOST_ENV_VAR_NAME = 'EXPO_PUBLIC_POSTHOG_HOST';
|
|
45
|
+
const POSTHOG_CLI_API_KEY_ENV_VAR_NAME = 'POSTHOG_CLI_API_KEY';
|
|
46
|
+
const POSTHOG_CLI_PROJECT_ID_ENV_VAR_NAME = 'POSTHOG_CLI_PROJECT_ID';
|
|
47
|
+
const POSTHOG_CLI_HOST_ENV_VAR_NAME = 'POSTHOG_CLI_HOST';
|
|
48
|
+
const PERSONAL_API_KEY_SETTINGS_PATH = '/settings/user-api-keys';
|
|
49
|
+
const ERROR_TRACKING_NEEDS_KEY_MESSAGE = `Error tracking needs a PostHog personal API key in non-interactive mode. Pass --posthog-cli-api-key (create one in PostHog under Settings → Personal API keys (${PERSONAL_API_KEY_SETTINGS_PATH}) with the "Source map upload" preset), or drop --error-tracking.`;
|
|
50
|
+
function getSpawnErrorOutput(error) {
|
|
51
|
+
const { stdout, stderr } = (error ?? {});
|
|
52
|
+
return `${stdout ?? ''}${stderr ?? ''}`;
|
|
53
|
+
}
|
|
54
|
+
class IntegrationsPostHogConnect extends EasCommand_1.default {
|
|
55
|
+
static description = 'connect PostHog to your Expo project';
|
|
56
|
+
static contextDefinition = {
|
|
57
|
+
...this.ContextOptions.ProjectConfig,
|
|
58
|
+
};
|
|
59
|
+
static flags = {
|
|
60
|
+
...flags_1.EasNonInteractiveAndJsonFlags,
|
|
61
|
+
region: core_1.Flags.string({
|
|
62
|
+
description: 'PostHog region',
|
|
63
|
+
options: POSTHOG_REGIONS.map(r => r.value),
|
|
64
|
+
}),
|
|
65
|
+
'session-replay': core_1.Flags.boolean({
|
|
66
|
+
allowNo: true,
|
|
67
|
+
description: 'Set up PostHog session replay (default: yes)',
|
|
68
|
+
}),
|
|
69
|
+
'error-tracking': core_1.Flags.boolean({
|
|
70
|
+
allowNo: true,
|
|
71
|
+
description: 'Set up PostHog error tracking / source maps (requires a personal API key)',
|
|
72
|
+
}),
|
|
73
|
+
'posthog-cli-api-key': core_1.Flags.string({
|
|
74
|
+
description: 'PostHog personal API key for error-tracking source-map uploads (enables error tracking non-interactively)',
|
|
75
|
+
}),
|
|
76
|
+
overwrite: core_1.Flags.boolean({
|
|
77
|
+
description: 'Overwrite existing PostHog environment variables without prompting',
|
|
78
|
+
default: false,
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
async runAsync() {
|
|
82
|
+
const { flags } = await this.parse(IntegrationsPostHogConnect);
|
|
83
|
+
const { region: regionFlag, overwrite } = flags;
|
|
84
|
+
const cliApiKeyFlag = (flags['posthog-cli-api-key'] ?? process.env[POSTHOG_CLI_API_KEY_ENV_VAR_NAME])?.trim() ||
|
|
85
|
+
undefined;
|
|
86
|
+
const { json: jsonFlag, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
|
|
87
|
+
if (jsonFlag) {
|
|
88
|
+
(0, json_1.enableJsonOutput)();
|
|
89
|
+
}
|
|
90
|
+
if (nonInteractive && flags['error-tracking'] === true && !cliApiKeyFlag) {
|
|
91
|
+
throw new Error(ERROR_TRACKING_NEEDS_KEY_MESSAGE);
|
|
92
|
+
}
|
|
93
|
+
const { privateProjectConfig: { projectId, exp, projectDir }, loggedIn: { graphqlClient }, } = await this.getContextAsync(IntegrationsPostHogConnect, {
|
|
94
|
+
nonInteractive,
|
|
95
|
+
withServerSideEnvironment: null,
|
|
96
|
+
});
|
|
97
|
+
const account = await (0, projectUtils_1.getOwnerAccountForProjectIdAsync)(graphqlClient, projectId);
|
|
98
|
+
let connection = await PostHogQuery_1.PostHogQuery.getPostHogOrganizationConnectionByAccountIdAsync(graphqlClient, account.id);
|
|
99
|
+
if (connection) {
|
|
100
|
+
if (regionFlag && regionFlag !== connection.posthogRegion) {
|
|
101
|
+
log_1.default.warn(`This account is already connected to PostHog in the ${connection.posthogRegion} region; --region ${regionFlag} is ignored. An account has a single PostHog organization and its region can't be changed.`);
|
|
102
|
+
}
|
|
103
|
+
log_1.default.withTick(`Using existing PostHog organization ${chalk_1.default.bold(connection.posthogOrganizationName)} (${connection.posthogRegion})`);
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
const region = await this.resolveRegionAsync(regionFlag, nonInteractive);
|
|
107
|
+
const spinner = (0, ora_1.ora)('Creating PostHog organization').start();
|
|
108
|
+
try {
|
|
109
|
+
connection = await PostHogMutation_1.PostHogMutation.createPostHogAccountRequestAsync(graphqlClient, {
|
|
110
|
+
accountId: account.id,
|
|
111
|
+
region,
|
|
112
|
+
});
|
|
113
|
+
spinner.succeed(`Created PostHog organization ${chalk_1.default.bold(connection.posthogOrganizationName)}`);
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
spinner.fail('Failed to create PostHog organization');
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
let project = await PostHogQuery_1.PostHogQuery.getPostHogProjectByAppIdAsync(graphqlClient, projectId);
|
|
121
|
+
if (project) {
|
|
122
|
+
log_1.default.withTick(`Using existing PostHog project ${chalk_1.default.bold(project.posthogProjectName)}`);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
const spinner = (0, ora_1.ora)('Setting up PostHog project').start();
|
|
126
|
+
try {
|
|
127
|
+
project = await PostHogMutation_1.PostHogMutation.setupPostHogProjectAsync(graphqlClient, {
|
|
128
|
+
appId: projectId,
|
|
129
|
+
posthogOrganizationConnectionId: connection.id,
|
|
130
|
+
});
|
|
131
|
+
spinner.succeed(`Created PostHog project ${chalk_1.default.bold(project.posthogProjectName)}`);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
spinner.fail('Failed to set up PostHog project');
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
const features = await this.resolveFeaturesAsync(flags, cliApiKeyFlag, nonInteractive);
|
|
139
|
+
const anyFeatureSelected = features.analytics || features.sessionReplay || features.errorTracking;
|
|
140
|
+
const envVars = [];
|
|
141
|
+
if (anyFeatureSelected) {
|
|
142
|
+
envVars.push({
|
|
143
|
+
name: EAS_POSTHOG_API_KEY_ENV_VAR_NAME,
|
|
144
|
+
value: project.posthogProjectToken,
|
|
145
|
+
visibility: generated_1.EnvironmentVariableVisibility.Public,
|
|
146
|
+
}, {
|
|
147
|
+
name: EAS_POSTHOG_HOST_ENV_VAR_NAME,
|
|
148
|
+
value: project.posthogHost,
|
|
149
|
+
visibility: generated_1.EnvironmentVariableVisibility.Public,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (features.errorTracking) {
|
|
153
|
+
const cliApiKey = await this.resolveCliApiKeyAsync(cliApiKeyFlag, project.posthogHost);
|
|
154
|
+
envVars.push({
|
|
155
|
+
name: POSTHOG_CLI_API_KEY_ENV_VAR_NAME,
|
|
156
|
+
value: cliApiKey,
|
|
157
|
+
visibility: generated_1.EnvironmentVariableVisibility.Sensitive,
|
|
158
|
+
}, {
|
|
159
|
+
name: POSTHOG_CLI_PROJECT_ID_ENV_VAR_NAME,
|
|
160
|
+
value: project.posthogProjectIdentifier,
|
|
161
|
+
visibility: generated_1.EnvironmentVariableVisibility.Public,
|
|
162
|
+
}, {
|
|
163
|
+
name: POSTHOG_CLI_HOST_ENV_VAR_NAME,
|
|
164
|
+
value: project.posthogHost,
|
|
165
|
+
visibility: generated_1.EnvironmentVariableVisibility.Public,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const manualSteps = [];
|
|
169
|
+
if (anyFeatureSelected) {
|
|
170
|
+
const packages = [...SDK_PACKAGES];
|
|
171
|
+
if (features.sessionReplay) {
|
|
172
|
+
packages.push(SESSION_REPLAY_PACKAGE);
|
|
173
|
+
}
|
|
174
|
+
const installResult = await this.installSdkPackagesAsync(projectDir, packages, jsonFlag);
|
|
175
|
+
if (installResult === 'failed') {
|
|
176
|
+
manualSteps.push(`The PostHog SDK packages didn't install. Run npx expo install ${packages.join(' ')} from your project directory.`);
|
|
177
|
+
}
|
|
178
|
+
const pluginManualStep = await this.addConfigPluginAsync(projectDir, exp);
|
|
179
|
+
if (pluginManualStep) {
|
|
180
|
+
manualSteps.push(pluginManualStep);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
await this.writeEnvLocalAsync(projectDir, envVars, nonInteractive, overwrite);
|
|
184
|
+
await Promise.all(envVars.map(envVar => this.upsertEasEnvVarAsync(graphqlClient, projectId, envVar, nonInteractive, overwrite)));
|
|
185
|
+
if (jsonFlag) {
|
|
186
|
+
(0, json_1.printJsonOnlyOutput)({
|
|
187
|
+
organizationConnection: {
|
|
188
|
+
id: connection.id,
|
|
189
|
+
name: connection.posthogOrganizationName,
|
|
190
|
+
region: connection.posthogRegion,
|
|
191
|
+
},
|
|
192
|
+
project: {
|
|
193
|
+
id: project.id,
|
|
194
|
+
name: project.posthogProjectName,
|
|
195
|
+
apiKey: project.posthogProjectToken,
|
|
196
|
+
host: project.posthogHost,
|
|
197
|
+
},
|
|
198
|
+
features,
|
|
199
|
+
dashboardUrl: (0, posthog_1.getPostHogProjectDashboardUrl)(project),
|
|
200
|
+
environmentVariables: envVars.map(v => v.name),
|
|
201
|
+
manualSteps,
|
|
202
|
+
});
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (!anyFeatureSelected) {
|
|
206
|
+
log_1.default.addNewLineIfNone();
|
|
207
|
+
log_1.default.log(chalk_1.default.green('PostHog project created.'));
|
|
208
|
+
log_1.default.newLine();
|
|
209
|
+
log_1.default.log(`${chalk_1.default.bold('Dashboard')}: ${(0, log_1.link)((0, posthog_1.getPostHogProjectDashboardUrl)(project), { dim: false })}`);
|
|
210
|
+
log_1.default.newLine();
|
|
211
|
+
log_1.default.warn('No PostHog features selected, so no SDK, config plugin, or environment variables were set up. Re-run to add analytics, session replay, or error tracking.');
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
this.printNextSteps(project, features, manualSteps);
|
|
215
|
+
}
|
|
216
|
+
async resolveFeaturesAsync(flags, cliApiKey, nonInteractive) {
|
|
217
|
+
const sessionReplayFlag = flags['session-replay'];
|
|
218
|
+
const errorTrackingFlag = flags['error-tracking'];
|
|
219
|
+
const hasCliApiKey = !!cliApiKey;
|
|
220
|
+
if (nonInteractive) {
|
|
221
|
+
const errorTracking = errorTrackingFlag ?? hasCliApiKey;
|
|
222
|
+
if (errorTrackingFlag === undefined && !hasCliApiKey) {
|
|
223
|
+
log_1.default.warn('Skipping error tracking (source maps) — it needs a personal API key. Re-run interactively, or pass --posthog-cli-api-key to enable it non-interactively.');
|
|
224
|
+
}
|
|
225
|
+
return { analytics: true, sessionReplay: sessionReplayFlag ?? true, errorTracking };
|
|
226
|
+
}
|
|
227
|
+
if (sessionReplayFlag !== undefined || errorTrackingFlag !== undefined) {
|
|
228
|
+
return {
|
|
229
|
+
analytics: true,
|
|
230
|
+
sessionReplay: sessionReplayFlag ?? true,
|
|
231
|
+
errorTracking: errorTrackingFlag ?? true,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const { features } = await (0, prompts_1.promptAsync)({
|
|
235
|
+
type: 'multiselect',
|
|
236
|
+
name: 'features',
|
|
237
|
+
message: 'PostHog features to set up',
|
|
238
|
+
choices: [
|
|
239
|
+
{ title: 'Analytics', value: 'analytics', selected: true },
|
|
240
|
+
{ title: 'Session replay', value: 'session-replay', selected: true },
|
|
241
|
+
{
|
|
242
|
+
title: 'Error tracking (source maps) — requires a personal API key',
|
|
243
|
+
value: 'error-tracking',
|
|
244
|
+
selected: true,
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
instructions: false,
|
|
248
|
+
min: 0,
|
|
249
|
+
});
|
|
250
|
+
const selected = new Set(features);
|
|
251
|
+
return {
|
|
252
|
+
analytics: selected.has('analytics'),
|
|
253
|
+
sessionReplay: selected.has('session-replay'),
|
|
254
|
+
errorTracking: selected.has('error-tracking'),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
async resolveCliApiKeyAsync(cliApiKey, host) {
|
|
258
|
+
if (cliApiKey) {
|
|
259
|
+
return cliApiKey;
|
|
260
|
+
}
|
|
261
|
+
const settingsUrl = `${host.replace(/\/$/, '')}${PERSONAL_API_KEY_SETTINGS_PATH}`;
|
|
262
|
+
const { apiKey } = await (0, prompts_1.promptAsync)({
|
|
263
|
+
type: 'password',
|
|
264
|
+
name: 'apiKey',
|
|
265
|
+
message: `Paste a PostHog personal API key for source-map uploads.\nCreate one at ${settingsUrl} using the "Source map upload" preset.`,
|
|
266
|
+
validate: (value) => (value.trim() ? true : 'Personal API key cannot be empty'),
|
|
267
|
+
});
|
|
268
|
+
return apiKey.trim();
|
|
269
|
+
}
|
|
270
|
+
async resolveRegionAsync(flagValue, nonInteractive) {
|
|
271
|
+
const flagRegion = POSTHOG_REGIONS.find(r => r.value === flagValue);
|
|
272
|
+
if (flagRegion) {
|
|
273
|
+
return flagRegion.value;
|
|
274
|
+
}
|
|
275
|
+
if (nonInteractive) {
|
|
276
|
+
throw new Error('A PostHog region is required in non-interactive mode. Pass --region US or --region EU. The region sets data residency and cannot be changed after connecting.');
|
|
277
|
+
}
|
|
278
|
+
return await (0, prompts_1.selectAsync)('Select a PostHog region', POSTHOG_REGIONS);
|
|
279
|
+
}
|
|
280
|
+
async installSdkPackagesAsync(projectDir, packages, jsonFlag) {
|
|
281
|
+
const spinner = jsonFlag ? null : (0, ora_1.ora)('Installing the PostHog SDK packages').start();
|
|
282
|
+
try {
|
|
283
|
+
await (0, spawn_async_1.default)('npx', ['expo', 'install', ...packages], { cwd: projectDir });
|
|
284
|
+
spinner?.succeed('Installed the PostHog SDK packages');
|
|
285
|
+
return 'installed';
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
const output = getSpawnErrorOutput(error);
|
|
289
|
+
log_1.default.debug(output || error);
|
|
290
|
+
if (output.includes('Cannot automatically write to dynamic config')) {
|
|
291
|
+
spinner?.succeed('Installed the PostHog SDK packages');
|
|
292
|
+
return 'installed';
|
|
293
|
+
}
|
|
294
|
+
spinner?.fail('Failed to install the PostHog SDK packages');
|
|
295
|
+
return 'failed';
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
async addConfigPluginAsync(projectDir, exp) {
|
|
299
|
+
const plugins = exp.plugins ?? [];
|
|
300
|
+
const alreadyAdded = plugins.some(p => (Array.isArray(p) ? p[0] : p) === CONFIG_PLUGIN);
|
|
301
|
+
if (alreadyAdded) {
|
|
302
|
+
log_1.default.withTick(`Config plugin ${chalk_1.default.bold(CONFIG_PLUGIN)} is already configured`);
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
const modification = await (0, expoConfig_1.createOrModifyExpoConfigAsync)(projectDir, { plugins: [...plugins, CONFIG_PLUGIN] }, { skipSDKVersionRequirement: true });
|
|
306
|
+
if (modification.type === 'success') {
|
|
307
|
+
log_1.default.withTick(`Added the ${chalk_1.default.bold(CONFIG_PLUGIN)} config plugin`);
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
if (modification.type === 'warn') {
|
|
311
|
+
return `${modification.message} Add ${JSON.stringify(CONFIG_PLUGIN)} to the "plugins" array in your app config.`;
|
|
312
|
+
}
|
|
313
|
+
return `Add ${JSON.stringify(CONFIG_PLUGIN)} to the "plugins" array in your app config.`;
|
|
314
|
+
}
|
|
315
|
+
async writeEnvLocalAsync(projectDir, envVars, nonInteractive, overwrite) {
|
|
316
|
+
if (envVars.length === 0) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const envPath = path_1.default.join(projectDir, '.env.local');
|
|
320
|
+
let rawContent = '';
|
|
321
|
+
if (await fs.pathExists(envPath)) {
|
|
322
|
+
rawContent = await fs.readFile(envPath, 'utf8');
|
|
323
|
+
const existing = dotenv_1.default.parse(rawContent);
|
|
324
|
+
const conflicts = envVars.filter(v => existing[v.name] !== undefined);
|
|
325
|
+
if (conflicts.length > 0 && !overwrite) {
|
|
326
|
+
if (nonInteractive) {
|
|
327
|
+
log_1.default.warn(`.env.local already defines ${conflicts
|
|
328
|
+
.map(v => v.name)
|
|
329
|
+
.join(', ')}; skipped (pass --overwrite to replace).`);
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
const confirmed = await (0, prompts_1.confirmAsync)({
|
|
333
|
+
message: `.env.local already defines ${conflicts
|
|
334
|
+
.map(v => v.name)
|
|
335
|
+
.join(', ')}. Overwrite with the PostHog values?`,
|
|
336
|
+
});
|
|
337
|
+
if (!confirmed) {
|
|
338
|
+
log_1.default.warn(`Skipped updating ${chalk_1.default.bold('.env.local')}.`);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
const updatedContent = this.mergeEnvContent(rawContent, Object.fromEntries(envVars.map(v => [v.name, v.value])));
|
|
344
|
+
await fs.writeFile(envPath, updatedContent);
|
|
345
|
+
log_1.default.withTick(`Wrote PostHog config to ${chalk_1.default.bold('.env.local')}`);
|
|
346
|
+
}
|
|
347
|
+
mergeEnvContent(rawContent, newVars) {
|
|
348
|
+
let content = rawContent;
|
|
349
|
+
const keysToAdd = { ...newVars };
|
|
350
|
+
for (const [key, value] of Object.entries(newVars)) {
|
|
351
|
+
const regex = new RegExp(`^${key}=.*$`, 'm');
|
|
352
|
+
if (regex.test(content)) {
|
|
353
|
+
content = content.replace(regex, () => `${key}=${value}`);
|
|
354
|
+
delete keysToAdd[key];
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const remaining = Object.entries(keysToAdd);
|
|
358
|
+
if (remaining.length > 0) {
|
|
359
|
+
if (content.length > 0 && !content.endsWith('\n')) {
|
|
360
|
+
content += '\n';
|
|
361
|
+
}
|
|
362
|
+
for (const [key, value] of remaining) {
|
|
363
|
+
content += `${key}=${value}\n`;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return content;
|
|
367
|
+
}
|
|
368
|
+
async upsertEasEnvVarAsync(graphqlClient, projectId, envVar, nonInteractive, overwrite) {
|
|
369
|
+
const existingVariables = await EnvironmentVariablesQuery_1.EnvironmentVariablesQuery.byAppIdAsync(graphqlClient, {
|
|
370
|
+
appId: projectId,
|
|
371
|
+
filterNames: [envVar.name],
|
|
372
|
+
});
|
|
373
|
+
const existingProjectVariable = existingVariables.find(variable => variable.scope === generated_1.EnvironmentVariableScope.Project);
|
|
374
|
+
if (existingProjectVariable) {
|
|
375
|
+
const shouldOverwrite = overwrite ||
|
|
376
|
+
(!nonInteractive &&
|
|
377
|
+
(await (0, prompts_1.confirmAsync)({
|
|
378
|
+
message: `EAS already has an ${envVar.name} environment variable for this project. Overwrite it?`,
|
|
379
|
+
})));
|
|
380
|
+
if (!shouldOverwrite) {
|
|
381
|
+
log_1.default.warn(`Skipped updating EAS environment variable ${chalk_1.default.bold(envVar.name)}${nonInteractive ? ' (pass --overwrite to replace it)' : ''}.`);
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
await EnvironmentVariableMutation_1.EnvironmentVariableMutation.updateAsync(graphqlClient, {
|
|
385
|
+
id: existingProjectVariable.id,
|
|
386
|
+
name: envVar.name,
|
|
387
|
+
value: envVar.value,
|
|
388
|
+
environments: EAS_POSTHOG_ENVIRONMENTS,
|
|
389
|
+
visibility: envVar.visibility,
|
|
390
|
+
type: generated_1.EnvironmentSecretType.String,
|
|
391
|
+
});
|
|
392
|
+
log_1.default.withTick(`Updated EAS environment variable ${chalk_1.default.bold(envVar.name)} for builds`);
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
await EnvironmentVariableMutation_1.EnvironmentVariableMutation.createForAppAsync(graphqlClient, {
|
|
396
|
+
name: envVar.name,
|
|
397
|
+
value: envVar.value,
|
|
398
|
+
environments: EAS_POSTHOG_ENVIRONMENTS,
|
|
399
|
+
visibility: envVar.visibility,
|
|
400
|
+
type: generated_1.EnvironmentSecretType.String,
|
|
401
|
+
}, projectId);
|
|
402
|
+
log_1.default.withTick(`Created EAS environment variable ${chalk_1.default.bold(envVar.name)} for builds`);
|
|
403
|
+
}
|
|
404
|
+
printNextSteps(project, features, manualSteps) {
|
|
405
|
+
log_1.default.addNewLineIfNone();
|
|
406
|
+
log_1.default.log(chalk_1.default.green('PostHog is connected!'));
|
|
407
|
+
log_1.default.newLine();
|
|
408
|
+
log_1.default.log(`${chalk_1.default.bold('Dashboard')}: ${(0, log_1.link)((0, posthog_1.getPostHogProjectDashboardUrl)(project), { dim: false })}`);
|
|
409
|
+
log_1.default.newLine();
|
|
410
|
+
log_1.default.log('Next steps:');
|
|
411
|
+
const steps = [
|
|
412
|
+
`Wrap your app in ${chalk_1.default.cyan('<PostHogProvider>')} following our guide (${chalk_1.default.cyan('https://docs.expo.dev/guides/using-posthog')}).`,
|
|
413
|
+
];
|
|
414
|
+
if (features.sessionReplay) {
|
|
415
|
+
steps.push(`Enable session replay in the provider options (the ${chalk_1.default.bold(SESSION_REPLAY_PACKAGE)} package is installed).`);
|
|
416
|
+
}
|
|
417
|
+
if (features.errorTracking) {
|
|
418
|
+
steps.push(`Error-tracking source maps: your ${chalk_1.default.bold('POSTHOG_CLI_*')} env vars are set. Wrap ${chalk_1.default.bold('metro.config.js')} with ${chalk_1.default.cyan('getPostHogExpoConfig')} (see ${chalk_1.default.cyan('https://docs.expo.dev/guides/using-posthog#source-maps')}); uploads then run automatically on EAS Build, and via ${chalk_1.default.cyan('posthog-cli hermes upload')} for EAS Update.`);
|
|
419
|
+
}
|
|
420
|
+
steps.forEach((step, index) => {
|
|
421
|
+
log_1.default.log(` ${index + 1}. ${step}`);
|
|
422
|
+
});
|
|
423
|
+
if (manualSteps.length > 0) {
|
|
424
|
+
log_1.default.newLine();
|
|
425
|
+
log_1.default.warn('Finish setup manually:');
|
|
426
|
+
manualSteps.forEach(step => {
|
|
427
|
+
log_1.default.warn(` • ${step}`);
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
exports.default = IntegrationsPostHogConnect;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import EasCommand from '../../../commandUtils/EasCommand';
|
|
2
|
+
export default class IntegrationsPostHogDashboard 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
|
+
};
|
|
8
|
+
static contextDefinition: {
|
|
9
|
+
loggedIn: import("../../../commandUtils/context/LoggedInContextField").default;
|
|
10
|
+
privateProjectConfig: import("../../../commandUtils/context/PrivateProjectConfigContextField").PrivateProjectConfigContextField;
|
|
11
|
+
};
|
|
12
|
+
runAsync(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const tslib_1 = require("tslib");
|
|
4
|
+
const better_opn_1 = tslib_1.__importDefault(require("better-opn"));
|
|
5
|
+
const EasCommand_1 = tslib_1.__importDefault(require("../../../commandUtils/EasCommand"));
|
|
6
|
+
const flags_1 = require("../../../commandUtils/flags");
|
|
7
|
+
const posthog_1 = require("../../../commandUtils/posthog");
|
|
8
|
+
const PostHogQuery_1 = require("../../../graphql/queries/PostHogQuery");
|
|
9
|
+
const log_1 = tslib_1.__importDefault(require("../../../log"));
|
|
10
|
+
const ora_1 = require("../../../ora");
|
|
11
|
+
const json_1 = require("../../../utils/json");
|
|
12
|
+
class IntegrationsPostHogDashboard extends EasCommand_1.default {
|
|
13
|
+
static description = 'open the PostHog dashboard for the linked PostHog project';
|
|
14
|
+
static flags = {
|
|
15
|
+
...flags_1.EasNonInteractiveAndJsonFlags,
|
|
16
|
+
};
|
|
17
|
+
static contextDefinition = {
|
|
18
|
+
...this.ContextOptions.ProjectConfig,
|
|
19
|
+
};
|
|
20
|
+
async runAsync() {
|
|
21
|
+
const { flags } = await this.parse(IntegrationsPostHogDashboard);
|
|
22
|
+
const { json: jsonFlag, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
|
|
23
|
+
if (jsonFlag) {
|
|
24
|
+
(0, json_1.enableJsonOutput)();
|
|
25
|
+
}
|
|
26
|
+
const { privateProjectConfig: { projectId, exp }, loggedIn: { graphqlClient }, } = await this.getContextAsync(IntegrationsPostHogDashboard, {
|
|
27
|
+
nonInteractive,
|
|
28
|
+
withServerSideEnvironment: null,
|
|
29
|
+
});
|
|
30
|
+
const posthogProject = await PostHogQuery_1.PostHogQuery.getPostHogProjectByAppIdAsync(graphqlClient, projectId);
|
|
31
|
+
if (!posthogProject) {
|
|
32
|
+
if (jsonFlag) {
|
|
33
|
+
(0, json_1.printJsonOnlyOutput)({ dashboardUrl: null });
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
(0, posthog_1.logNoPostHogProject)(exp.slug);
|
|
37
|
+
}
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const dashboardUrl = (0, posthog_1.getPostHogProjectDashboardUrl)(posthogProject);
|
|
41
|
+
if (jsonFlag) {
|
|
42
|
+
(0, json_1.printJsonOnlyOutput)({ dashboardUrl });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (nonInteractive) {
|
|
46
|
+
log_1.default.log(dashboardUrl);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const failedMessage = `Unable to open a web browser. PostHog dashboard is available at: ${dashboardUrl}`;
|
|
50
|
+
const spinner = (0, ora_1.ora)(`Opening ${dashboardUrl}`).start();
|
|
51
|
+
try {
|
|
52
|
+
const opened = await (0, better_opn_1.default)(dashboardUrl);
|
|
53
|
+
if (opened) {
|
|
54
|
+
spinner.succeed(`Opened ${dashboardUrl}`);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
spinner.fail(failedMessage);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
spinner.fail(failedMessage);
|
|
62
|
+
throw error;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.default = IntegrationsPostHogDashboard;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import EasCommand from '../../../commandUtils/EasCommand';
|
|
2
|
+
export default class IntegrationsPostHogDisconnect extends EasCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static flags: {
|
|
5
|
+
yes: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
6
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
7
|
+
'non-interactive': import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
static contextDefinition: {
|
|
10
|
+
loggedIn: import("../../../commandUtils/context/LoggedInContextField").default;
|
|
11
|
+
privateProjectConfig: import("../../../commandUtils/context/PrivateProjectConfigContextField").PrivateProjectConfigContextField;
|
|
12
|
+
};
|
|
13
|
+
runAsync(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
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 chalk_1 = tslib_1.__importDefault(require("chalk"));
|
|
6
|
+
const EasCommand_1 = tslib_1.__importDefault(require("../../../commandUtils/EasCommand"));
|
|
7
|
+
const flags_1 = require("../../../commandUtils/flags");
|
|
8
|
+
const posthog_1 = require("../../../commandUtils/posthog");
|
|
9
|
+
const PostHogMutation_1 = require("../../../graphql/mutations/PostHogMutation");
|
|
10
|
+
const PostHogQuery_1 = require("../../../graphql/queries/PostHogQuery");
|
|
11
|
+
const log_1 = tslib_1.__importDefault(require("../../../log"));
|
|
12
|
+
const ora_1 = require("../../../ora");
|
|
13
|
+
const prompts_1 = require("../../../prompts");
|
|
14
|
+
const json_1 = require("../../../utils/json");
|
|
15
|
+
class IntegrationsPostHogDisconnect extends EasCommand_1.default {
|
|
16
|
+
static description = 'remove the PostHog project link for the current Expo app from EAS servers';
|
|
17
|
+
static flags = {
|
|
18
|
+
...flags_1.EasNonInteractiveAndJsonFlags,
|
|
19
|
+
yes: core_1.Flags.boolean({
|
|
20
|
+
char: 'y',
|
|
21
|
+
description: 'Skip confirmation prompt',
|
|
22
|
+
default: false,
|
|
23
|
+
}),
|
|
24
|
+
};
|
|
25
|
+
static contextDefinition = {
|
|
26
|
+
...this.ContextOptions.ProjectConfig,
|
|
27
|
+
};
|
|
28
|
+
async runAsync() {
|
|
29
|
+
const { flags } = await this.parse(IntegrationsPostHogDisconnect);
|
|
30
|
+
const { yes } = flags;
|
|
31
|
+
const { json: jsonFlag, nonInteractive } = (0, flags_1.resolveNonInteractiveAndJsonFlags)(flags);
|
|
32
|
+
if (jsonFlag) {
|
|
33
|
+
(0, json_1.enableJsonOutput)();
|
|
34
|
+
}
|
|
35
|
+
const { privateProjectConfig: { projectId, exp }, loggedIn: { graphqlClient }, } = await this.getContextAsync(IntegrationsPostHogDisconnect, {
|
|
36
|
+
nonInteractive,
|
|
37
|
+
withServerSideEnvironment: null,
|
|
38
|
+
});
|
|
39
|
+
const posthogProject = await PostHogQuery_1.PostHogQuery.getPostHogProjectByAppIdAsync(graphqlClient, projectId);
|
|
40
|
+
if (!posthogProject) {
|
|
41
|
+
if (jsonFlag) {
|
|
42
|
+
(0, json_1.printJsonOnlyOutput)({ id: null });
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
(0, posthog_1.logNoPostHogProject)(exp.slug);
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!jsonFlag) {
|
|
50
|
+
log_1.default.addNewLineIfNone();
|
|
51
|
+
log_1.default.log((0, posthog_1.formatPostHogProject)(posthogProject));
|
|
52
|
+
log_1.default.newLine();
|
|
53
|
+
}
|
|
54
|
+
if (!nonInteractive && !yes) {
|
|
55
|
+
const confirmed = await (0, prompts_1.confirmAsync)({
|
|
56
|
+
message: 'Remove this PostHog project link from EAS servers? This does not delete the project on PostHog.',
|
|
57
|
+
});
|
|
58
|
+
if (!confirmed) {
|
|
59
|
+
log_1.default.warn('Canceled removal of the PostHog project link.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else if (!jsonFlag) {
|
|
64
|
+
log_1.default.warn('Removing the PostHog project link from EAS servers. This does not delete the project on PostHog.');
|
|
65
|
+
}
|
|
66
|
+
const spinner = jsonFlag ? null : (0, ora_1.ora)('Removing PostHog project link').start();
|
|
67
|
+
try {
|
|
68
|
+
await PostHogMutation_1.PostHogMutation.deletePostHogProjectAsync(graphqlClient, posthogProject.id);
|
|
69
|
+
spinner?.succeed(`Removed PostHog project ${chalk_1.default.bold(posthogProject.posthogProjectName)} from EAS servers`);
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
spinner?.fail('Failed to remove PostHog project link');
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
if (jsonFlag) {
|
|
76
|
+
(0, json_1.printJsonOnlyOutput)({ id: posthogProject.id, name: posthogProject.posthogProjectName });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
exports.default = IntegrationsPostHogDisconnect;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import EasCommand from '../../commandUtils/EasCommand';
|
|
2
|
+
export default class ObserveSession extends EasCommand {
|
|
3
|
+
static description: string;
|
|
4
|
+
static args: {
|
|
5
|
+
sessionId: 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
|
+
'project-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
11
|
+
};
|
|
12
|
+
static contextDefinition: {
|
|
13
|
+
loggedIn: import("../../commandUtils/context/LoggedInContextField").default;
|
|
14
|
+
projectId: import("../../commandUtils/context/ProjectIdContextField").ProjectIdContextField;
|
|
15
|
+
};
|
|
16
|
+
private static loggedInOnlyContextDefinition;
|
|
17
|
+
runAsync(): Promise<void>;
|
|
18
|
+
}
|