@posthog/wizard
Version:
The PostHog wizard helps you to configure your project
671 lines (667 loc) β’ 29.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.abort = abort;
exports.abortIfCancelled = abortIfCancelled;
exports.printWelcome = printWelcome;
exports.confirmContinueIfNoOrDirtyGitRepo = confirmContinueIfNoOrDirtyGitRepo;
exports.isInGitRepo = isInGitRepo;
exports.getUncommittedOrUntrackedFiles = getUncommittedOrUntrackedFiles;
exports.askForItemSelection = askForItemSelection;
exports.confirmContinueIfPackageVersionNotSupported = confirmContinueIfPackageVersionNotSupported;
exports.isReact19Installed = isReact19Installed;
exports.installPackage = installPackage;
exports.ensurePackageIsInstalled = ensurePackageIsInstalled;
exports.getPackageDotJson = getPackageDotJson;
exports.updatePackageDotJson = updatePackageDotJson;
exports.getPackageManager = getPackageManager;
exports.isUsingTypeScript = isUsingTypeScript;
exports.getOrAskForProjectData = getOrAskForProjectData;
exports.askForToolConfigPath = askForToolConfigPath;
exports.showCopyPasteInstructions = showCopyPasteInstructions;
exports.makeCodeSnippet = makeCodeSnippet;
exports.createNewConfigFile = createNewConfigFile;
exports.featureSelectionPrompt = featureSelectionPrompt;
exports.askShouldInstallPackage = askShouldInstallPackage;
exports.askShouldAddPackageOverride = askShouldAddPackageOverride;
exports.askForAIConsent = askForAIConsent;
exports.askForCloudRegion = askForCloudRegion;
const childProcess = __importStar(require("node:child_process"));
const fs = __importStar(require("node:fs"));
const os = __importStar(require("node:os"));
const node_path_1 = require("node:path");
const node_timers_1 = require("node:timers");
const node_url_1 = require("node:url");
const axios_1 = __importDefault(require("axios"));
const chalk_1 = __importDefault(require("chalk"));
const opn_1 = __importDefault(require("opn"));
const telemetry_1 = require("../telemetry");
const debug_1 = require("./debug");
const package_json_1 = require("./package-json");
const package_manager_1 = require("./package-manager");
const semver_1 = require("./semver");
const package_json_2 = require("./package-json");
const constants_1 = require("../lib/constants");
const analytics_1 = require("./analytics");
const clack_1 = __importDefault(require("./clack"));
const urls_1 = require("./urls");
const config_1 = require("../lib/config");
async function abort(message, status) {
await analytics_1.analytics.shutdown('cancelled');
clack_1.default.outro(message ?? 'Wizard setup cancelled.');
return process.exit(status ?? 1);
}
async function abortIfCancelled(input, integration) {
await analytics_1.analytics.shutdown('cancelled');
if (clack_1.default.isCancel(await input)) {
const docsUrl = integration
? config_1.INTEGRATION_CONFIG[integration].docsUrl
: 'https://posthog.com/docs';
clack_1.default.cancel(`Wizard setup cancelled. You can read the documentation for ${integration ?? 'PostHog'} at ${chalk_1.default.cyan(docsUrl)} to continue with the setup manually.`);
process.exit(0);
}
else {
return input;
}
}
function printWelcome(options) {
// eslint-disable-next-line no-console
console.log('');
clack_1.default.intro(chalk_1.default.inverse(` ${options.wizardName} `));
const welcomeText = options.message ||
`The ${options.wizardName} will help you set up PostHog for your application.\nThank you for using PostHog :)`;
clack_1.default.note(welcomeText);
}
async function confirmContinueIfNoOrDirtyGitRepo(options) {
return (0, telemetry_1.traceStep)('check-git-status', async () => {
if (!isInGitRepo()) {
const continueWithoutGit = options.default
? true
: await abortIfCancelled(clack_1.default.confirm({
message: 'You are not inside a git repository. The wizard will create and update files. Do you want to continue anyway?',
}));
analytics_1.analytics.setTag('continue-without-git', continueWithoutGit);
if (!continueWithoutGit) {
await abort(undefined, 0);
}
// return early to avoid checking for uncommitted files
return;
}
const uncommittedOrUntrackedFiles = getUncommittedOrUntrackedFiles();
if (uncommittedOrUntrackedFiles.length) {
clack_1.default.log.warn(`You have uncommitted or untracked files in your repo:
${uncommittedOrUntrackedFiles.join('\n')}
The wizard will create and update files.`);
const continueWithDirtyRepo = await abortIfCancelled(clack_1.default.confirm({
message: 'Do you want to continue anyway?',
}));
analytics_1.analytics.setTag('continue-with-dirty-repo', continueWithDirtyRepo);
if (!continueWithDirtyRepo) {
await abort(undefined, 0);
}
}
});
}
function isInGitRepo() {
try {
childProcess.execSync('git rev-parse --is-inside-work-tree', {
stdio: 'ignore',
});
return true;
}
catch {
return false;
}
}
function getUncommittedOrUntrackedFiles() {
try {
const gitStatus = childProcess
.execSync('git status --porcelain=v1', {
// we only care about stdout
stdio: ['ignore', 'pipe', 'ignore'],
})
.toString();
const files = gitStatus
.split(os.EOL)
.map((line) => line.trim())
.filter(Boolean)
.map((f) => `- ${f.split(/\s+/)[1]}`);
return files;
}
catch {
return [];
}
}
async function askForItemSelection(items, message) {
const selection = await abortIfCancelled(clack_1.default.select({
maxItems: 12,
message: message,
options: items.map((item, index) => {
return {
value: { value: item, index: index },
label: item,
};
}),
}));
return selection;
}
async function confirmContinueIfPackageVersionNotSupported({ packageId, packageName, packageVersion, acceptableVersions, note, }) {
return (0, telemetry_1.traceStep)(`check-package-version`, async () => {
analytics_1.analytics.setTag(`${packageName.toLowerCase()}-version`, packageVersion);
const isSupportedVersion = (0, semver_1.fulfillsVersionRange)({
acceptableVersions,
version: packageVersion,
canBeLatest: true,
});
if (isSupportedVersion) {
analytics_1.analytics.setTag(`${packageName.toLowerCase()}-supported`, true);
return;
}
clack_1.default.log.warn(`You have an unsupported version of ${packageName} installed:
${packageId}@${packageVersion}`);
clack_1.default.note(note ??
`Please upgrade to ${acceptableVersions} if you wish to use the PostHog wizard.`);
const continueWithUnsupportedVersion = await abortIfCancelled(clack_1.default.confirm({
message: 'Do you want to continue anyway?',
}));
analytics_1.analytics.setTag(`${packageName.toLowerCase()}-continue-with-unsupported-version`, continueWithUnsupportedVersion);
if (!continueWithUnsupportedVersion) {
await abort(undefined, 0);
}
});
}
async function isReact19Installed({ installDir, }) {
try {
const packageJson = await getPackageDotJson({ installDir });
const reactVersion = (0, package_json_2.getPackageVersion)('react', packageJson);
if (!reactVersion) {
return false;
}
return (0, semver_1.fulfillsVersionRange)({
version: reactVersion,
acceptableVersions: '>=19.0.0',
canBeLatest: true,
});
}
catch (error) {
return false;
}
}
/**
* Installs or updates a package with the user's package manager.
*
* IMPORTANT: This function modifies the `package.json`! Be sure to re-read
* it if you make additional modifications to it after calling this function!
*/
async function installPackage({ packageName, alreadyInstalled, askBeforeUpdating = true, packageNameDisplayLabel, packageManager, forceInstall = false, integration, installDir, }) {
return (0, telemetry_1.traceStep)('install-package', async () => {
if (alreadyInstalled && askBeforeUpdating) {
const shouldUpdatePackage = await abortIfCancelled(clack_1.default.confirm({
message: `The ${chalk_1.default.bold.cyan(packageNameDisplayLabel ?? packageName)} package is already installed. Do you want to update it to the latest version?`,
}));
if (!shouldUpdatePackage) {
return {};
}
}
const sdkInstallSpinner = clack_1.default.spinner();
const pkgManager = packageManager || (await getPackageManager({ installDir }));
// Most packages aren't compatible with React 19 yet, skip strict peer dependency checks if needed.
const isReact19 = await isReact19Installed({ installDir });
const legacyPeerDepsFlag = isReact19 && pkgManager.name === 'npm' ? '--legacy-peer-deps' : '';
sdkInstallSpinner.start(`${alreadyInstalled ? 'Updating' : 'Installing'} ${chalk_1.default.bold.cyan(packageNameDisplayLabel ?? packageName)} with ${chalk_1.default.bold(pkgManager.label)}.`);
try {
await new Promise((resolve, reject) => {
childProcess.exec(`${pkgManager.installCommand} ${packageName} ${pkgManager.flags} ${forceInstall ? pkgManager.forceInstallFlag : ''} ${legacyPeerDepsFlag}`.trim(), { cwd: installDir }, (err, stdout, stderr) => {
if (err) {
// Write a log file so we can better troubleshoot issues
fs.writeFileSync((0, node_path_1.join)(process.cwd(), `posthog-wizard-installation-error-${Date.now()}.log`), JSON.stringify({
stdout,
stderr,
}), { encoding: 'utf8' });
reject(err);
}
else {
resolve();
}
});
});
}
catch (e) {
sdkInstallSpinner.stop('Installation failed.');
clack_1.default.log.error(`${chalk_1.default.red('Encountered the following error during installation:')}\n\n${e}\n\n${chalk_1.default.dim(`The wizard has created a \`posthog-wizard-installation-error-*.log\` file. If you think this issue is caused by the PostHog wizard, create an issue on GitHub and include the log file's content:\n${constants_1.ISSUES_URL}`)}`);
await abort();
}
sdkInstallSpinner.stop(`${alreadyInstalled ? 'Updated' : 'Installed'} ${chalk_1.default.bold.cyan(packageNameDisplayLabel ?? packageName)} with ${chalk_1.default.bold(pkgManager.label)}.`);
analytics_1.analytics.capture('wizard interaction', {
action: 'package installed',
package_name: packageName,
package_manager: pkgManager.name,
integration,
});
return { packageManager: pkgManager };
});
}
/**
* Checks if @param packageId is listed as a dependency in @param packageJson.
* If not, it will ask users if they want to continue without the package.
*
* Use this function to check if e.g. a the framework of the SDK is installed
*
* @param packageJson the package.json object
* @param packageId the npm name of the package
* @param packageName a human readable name of the package
*/
async function ensurePackageIsInstalled(packageJson, packageId, packageName) {
return (0, telemetry_1.traceStep)('ensure-package-installed', async () => {
const installed = (0, package_json_1.hasPackageInstalled)(packageId, packageJson);
analytics_1.analytics.setTag(`${packageName.toLowerCase()}-installed`, installed);
if (!installed) {
const continueWithoutPackage = await abortIfCancelled(clack_1.default.confirm({
message: `${packageName} does not seem to be installed. Do you still want to continue?`,
initialValue: false,
}));
if (!continueWithoutPackage) {
await abort(undefined, 0);
}
}
});
}
async function getPackageDotJson({ installDir, }) {
const packageJsonFileContents = await fs.promises
.readFile((0, node_path_1.join)(installDir, 'package.json'), 'utf8')
.catch(() => {
clack_1.default.log.error('Could not find package.json. Make sure to run the wizard in the root of your app!');
return abort();
});
let packageJson = undefined;
try {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
packageJson = JSON.parse(packageJsonFileContents);
}
catch {
clack_1.default.log.error(`Unable to parse your ${chalk_1.default.cyan('package.json')}. Make sure it has a valid format!`);
await abort();
}
return packageJson || {};
}
async function updatePackageDotJson(packageDotJson, { installDir }) {
try {
await fs.promises.writeFile((0, node_path_1.join)(installDir, 'package.json'),
// TODO: maybe figure out the original indentation
JSON.stringify(packageDotJson, null, 2), {
encoding: 'utf8',
flag: 'w',
});
}
catch {
clack_1.default.log.error(`Unable to update your ${chalk_1.default.cyan('package.json')}.`);
await abort();
}
}
async function getPackageManager({ installDir, }) {
const detectedPackageManagers = (0, package_manager_1.detectAllPackageManagers)({ installDir });
// If exactly one package manager detected, use it automatically
if (detectedPackageManagers.length === 1) {
const detectedPackageManager = detectedPackageManagers[0];
analytics_1.analytics.setTag('package-manager', detectedPackageManager.name);
return detectedPackageManager;
}
// If multiple or no package managers detected, prompt user to select
const options = detectedPackageManagers.length > 0
? detectedPackageManagers
: package_manager_1.packageManagers;
const message = detectedPackageManagers.length > 1
? 'Multiple package managers detected. Please select one:'
: 'Please select your package manager.';
const selectedPackageManager = await abortIfCancelled(clack_1.default.select({
message,
options: options.map((packageManager) => ({
value: packageManager,
label: packageManager.label,
})),
}));
analytics_1.analytics.setTag('package-manager', selectedPackageManager.name);
return selectedPackageManager;
}
function isUsingTypeScript({ installDir, }) {
try {
return fs.existsSync((0, node_path_1.join)(installDir, 'tsconfig.json'));
}
catch {
return false;
}
}
/**
*
* Use this function to get project data for the wizard.
*
* @param options wizard options
* @returns project data (token, url)
*/
async function getOrAskForProjectData(_options) {
const cloudUrl = (0, urls_1.getCloudUrlFromRegion)(_options.cloudRegion);
const { host, projectApiKey, wizardHash } = await (0, telemetry_1.traceStep)('login', () => askForWizardLogin({
url: cloudUrl,
signup: _options.signup,
}));
if (!projectApiKey) {
clack_1.default.log.error(`Didn't receive a project API key. This shouldn't happen :(
Please let us know if you think this is a bug in the wizard:
${chalk_1.default.cyan(constants_1.ISSUES_URL)}`);
clack_1.default.log
.info(`In the meantime, we'll add a dummy project API key (${chalk_1.default.cyan(`"${constants_1.DUMMY_PROJECT_API_KEY}"`)}) for you to replace later.
You can find your Project API key here:
${chalk_1.default.cyan(`${cloudUrl}/settings/project#variables`)}`);
}
return {
wizardHash,
host: host || constants_1.DEFAULT_HOST_URL,
projectApiKey: projectApiKey || constants_1.DUMMY_PROJECT_API_KEY,
};
}
async function askForWizardLogin(options) {
let wizardHash;
try {
wizardHash = (await axios_1.default.post(`${options.url}/api/wizard/initialize`)).data.hash;
}
catch (e) {
clack_1.default.log.error('Loading wizard failed.');
clack_1.default.log.info(JSON.stringify(e, null, 2));
await abort(chalk_1.default.red(`Please try again in a few minutes and let us know if this issue persists: ${constants_1.ISSUES_URL}`));
throw e;
}
const loginUrl = new node_url_1.URL(`${options.url}/wizard?hash=${wizardHash}`);
const signupUrl = new node_url_1.URL(`${options.url}/signup?next=${encodeURIComponent(`/wizard?hash=${wizardHash}`)}`);
const urlToOpen = options.signup ? signupUrl.toString() : loginUrl.toString();
clack_1.default.log.info(`${chalk_1.default.bold(`If the browser window didn't open automatically, please open the following link to login into PostHog:`)}\n\n${chalk_1.default.cyan(urlToOpen)}${options.signup
? `\n\nIf you already have an account, you can use this link:\n\n${chalk_1.default.cyan(loginUrl.toString())}`
: ``}`);
if (process.env.NODE_ENV !== 'test') {
(0, opn_1.default)(urlToOpen, { wait: false }).catch(() => {
// opn throws in environments that don't have a browser (e.g. remote shells) so we just noop here
});
}
const loginSpinner = clack_1.default.spinner();
loginSpinner.start('Waiting for you to log in using the link above');
const data = await new Promise((resolve) => {
const pollingInterval = (0, node_timers_1.setInterval)(() => {
axios_1.default
.get(`${options.url}/api/wizard/data`, {
headers: {
'Accept-Encoding': 'deflate',
'X-PostHog-Wizard-Hash': wizardHash,
},
})
.then((result) => {
const data = {
wizardHash,
projectApiKey: result.data.project_api_key,
host: result.data.host,
distinctId: result.data.user_distinct_id,
};
resolve(data);
clearTimeout(timeout);
clearInterval(pollingInterval);
})
.catch(() => {
// noop - just try again
});
}, 500);
const timeout = setTimeout(() => {
clearInterval(pollingInterval);
loginSpinner.stop('Login timed out. No worries - it happens to the best of us.');
analytics_1.analytics.setTag('opened-wizard-link', false);
void abort('Please restart the wizard and log in to complete the setup.');
}, 180_000);
});
loginSpinner.stop(`Login complete. ${options.signup ? 'Welcome to PostHog! π' : ''}`);
analytics_1.analytics.setTag('opened-wizard-link', true);
analytics_1.analytics.setDistinctId(data.distinctId);
return data;
}
/**
* Asks users if they have a config file for @param tool (e.g. Vite).
* If yes, asks users to specify the path to their config file.
*
* Use this helper function as a fallback mechanism if the lookup for
* a config file with its most usual location/name fails.
*
* @param toolName Name of the tool for which we're looking for the config file
* @param configFileName Name of the most common config file name (e.g. vite.config.js)
*
* @returns a user path to the config file or undefined if the user doesn't have a config file
*/
async function askForToolConfigPath(toolName, configFileName) {
const hasConfig = await abortIfCancelled(clack_1.default.confirm({
message: `Do you have a ${toolName} config file (e.g. ${chalk_1.default.cyan(configFileName)})?`,
initialValue: true,
}));
if (!hasConfig) {
return undefined;
}
return await abortIfCancelled(clack_1.default.text({
message: `Please enter the path to your ${toolName} config file:`,
placeholder: (0, node_path_1.join)('.', configFileName),
validate: (value) => {
if (!value) {
return 'Please enter a path.';
}
try {
fs.accessSync(value);
}
catch {
return 'Could not access the file at this path.';
}
},
}));
}
/**
* Prints copy/paste-able instructions to the console.
* Afterwards asks the user if they added the code snippet to their file.
*
* While there's no point in providing a "no" answer here, it gives users time to fulfill the
* task before the wizard continues with additional steps.
*
* Use this function if you want to show users instructions on how to add/modify
* code in their file. This is helpful if automatic insertion failed or is not possible/feasible.
*
* @param filename the name of the file to which the code snippet should be applied.
* If a path is provided, only the filename will be used.
*
* @param codeSnippet the snippet to be printed. Use {@link makeCodeSnippet} to create the
* diff-like format for visually highlighting unchanged or modified lines of code.
*
* @param hint (optional) a hint to be printed after the main instruction to add
* the code from @param codeSnippet to their @param filename.
*
* TODO: refactor copy paste instructions across different wizards to use this function.
* this might require adding a custom message parameter to the function
*/
async function showCopyPasteInstructions(filename, codeSnippet, hint) {
clack_1.default.log.step(`Add the following code to your ${chalk_1.default.cyan((0, node_path_1.basename)(filename))} file:${hint ? chalk_1.default.dim(` (${chalk_1.default.dim(hint)})`) : ''}`);
// Padding the code snippet to be printed with a \n at the beginning and end
// This makes it easier to distinguish the snippet from the rest of the output
// Intentionally logging directly to console here so that the code can be copied/pasted directly
// eslint-disable-next-line no-console
console.log(`\n${codeSnippet}\n`);
await abortIfCancelled(clack_1.default.select({
message: 'Did you apply the snippet above?',
options: [{ label: 'Yes, continue!', value: true }],
initialValue: true,
}));
}
/**
* Crafts a code snippet that can be used to e.g.
* - print copy/paste instructions to the console
* - create a new config file.
*
* @param colors set this to true if you want the final snippet to be colored.
* This is useful for printing the snippet to the console as part of copy/paste instructions.
*
* @param callback the callback that returns the formatted code snippet.
* It exposes takes the helper functions for marking code as unchanged, new or removed.
* These functions no-op if no special formatting should be applied
* and otherwise apply the appropriate formatting/coloring.
* (@see {@link CodeSnippetFormatter})
*
* @see {@link showCopyPasteInstructions} for the helper with which to display the snippet in the console.
*
* @returns a string containing the final, formatted code snippet.
*/
function makeCodeSnippet(colors, callback) {
const unchanged = (txt) => (colors ? chalk_1.default.grey(txt) : txt);
const plus = (txt) => (colors ? chalk_1.default.greenBright(txt) : txt);
const minus = (txt) => (colors ? chalk_1.default.redBright(txt) : txt);
return callback(unchanged, plus, minus);
}
/**
* Creates a new config file with the given @param filepath and @param codeSnippet.
*
* Use this function to create a new config file for users. This is useful
* when users answered that they don't yet have a config file for a tool.
*
* (This doesn't mean that they don't yet have some other way of configuring
* their tool but we can leave it up to them to figure out how to merge configs
* here.)
*
* @param filepath absolute path to the new config file
* @param codeSnippet the snippet to be inserted into the file
* @param moreInformation (optional) the message to be printed after the file was created
* For example, this can be a link to more information about configuring the tool.
*
* @returns true on success, false otherwise
*/
async function createNewConfigFile(filepath, codeSnippet, { installDir }, moreInformation) {
if (!(0, node_path_1.isAbsolute)(filepath)) {
(0, debug_1.debug)(`createNewConfigFile: filepath is not absolute: ${filepath}`);
return false;
}
const prettyFilename = chalk_1.default.cyan((0, node_path_1.relative)(installDir, filepath));
try {
await fs.promises.writeFile(filepath, codeSnippet);
clack_1.default.log.success(`Added new ${prettyFilename} file.`);
if (moreInformation) {
clack_1.default.log.info(chalk_1.default.gray(moreInformation));
}
return true;
}
catch (e) {
(0, debug_1.debug)(e);
clack_1.default.log.warn(`Could not create a new ${prettyFilename} file. Please create one manually and follow the instructions below.`);
}
return false;
}
async function featureSelectionPrompt(features) {
return (0, telemetry_1.traceStep)('feature-selection', async () => {
const selectedFeatures = {};
for (const feature of features) {
const selected = await abortIfCancelled(clack_1.default.select({
message: feature.prompt,
initialValue: true,
options: [
{
value: true,
label: 'Yes',
hint: feature.enabledHint,
},
{
value: false,
label: 'No',
hint: feature.disabledHint,
},
],
}));
selectedFeatures[feature.id] = selected;
}
return selectedFeatures;
});
}
async function askShouldInstallPackage(pkgName) {
return (0, telemetry_1.traceStep)(`ask-install-package`, () => abortIfCancelled(clack_1.default.confirm({
message: `Do you want to install ${chalk_1.default.cyan(pkgName)}?`,
})));
}
async function askShouldAddPackageOverride(pkgName, pkgVersion) {
return (0, telemetry_1.traceStep)(`ask-add-package-override`, () => abortIfCancelled(clack_1.default.confirm({
message: `Do you want to add an override for ${chalk_1.default.cyan(pkgName)} version ${chalk_1.default.cyan(pkgVersion)}?`,
})));
}
async function askForAIConsent(options) {
return await (0, telemetry_1.traceStep)('ask-for-ai-consent', async () => {
const aiConsent = options.default
? true
: await abortIfCancelled(clack_1.default.select({
message: 'This setup wizard uses AI, are you happy to continue? β¨',
options: [
{
label: 'Yes',
value: true,
hint: 'We will use AI to help you setup PostHog quickly',
},
{
label: 'No',
value: false,
hint: "I don't like AI",
},
],
initialValue: true,
}));
return aiConsent;
});
}
async function askForCloudRegion() {
return await (0, telemetry_1.traceStep)('ask-for-cloud-region', async () => {
const cloudRegion = await abortIfCancelled(clack_1.default.select({
message: 'Select your PostHog Cloud region',
options: [
{
label: 'US πΊπΈ',
value: 'us',
hint: 'Your data will be stored in the US',
},
{
label: 'EU πͺπΊ',
value: 'eu',
hint: 'Your data will be stored in the EU',
},
],
}));
return cloudRegion;
});
}
//# sourceMappingURL=clack-utils.js.map