@posthog/wizard
Version:
The PostHog wizard helps you to configure your project
711 lines (707 loc) β’ 31.2 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.tryGetPackageJson = tryGetPackageJson;
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 chalk_1 = __importDefault(require("chalk"));
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");
const oauth_1 = require("./oauth");
const api_1 = require("../lib/api");
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');
const resolvedInput = await input;
if (clack_1.default.isCancel(resolvedInput) ||
(typeof resolvedInput === 'symbol' &&
resolvedInput.description === 'clack:cancel')) {
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()) {
// CI mode: auto-continue without git
const continueWithoutGit = options.default || options.ci
? 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) {
// CI mode: auto-continue with dirty repo
if (options.ci) {
clack_1.default.log.info(`CI mode: continuing with uncommitted/untracked files in repo`);
analytics_1.analytics.setTag('continue-with-dirty-repo', true);
return;
}
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 || {};
}
/**
* Try to get package.json, returning null if it doesn't exist.
* Use this for detection purposes where missing package.json is expected (e.g., Python projects).
*/
async function tryGetPackageJson({ installDir, }) {
try {
const packageJsonFileContents = await fs.promises.readFile((0, node_path_1.join)(installDir, 'package.json'), 'utf8');
return JSON.parse(packageJsonFileContents);
}
catch {
return null;
}
}
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(options) {
const detectedPackageManagers = (0, package_manager_1.detectAllPackageManagers)({
installDir: options.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;
}
// CI mode: auto-select first detected or npm
if (options.ci) {
const selectedPackageManager = detectedPackageManagers.length > 0 ? detectedPackageManagers[0] : package_manager_1.NPM;
clack_1.default.log.info(`CI mode: auto-selected package manager: ${selectedPackageManager.label}`);
analytics_1.analytics.setTag('package-manager', selectedPackageManager.name);
return selectedPackageManager;
}
// If multiple or no package managers detected, prompt user to select
const pkgOptions = 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: pkgOptions.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);
// CI mode: bypass OAuth, use personal API key for LLM gateway
if (_options.ci && _options.apiKey) {
const host = (0, urls_1.getHostFromRegion)(_options.cloudRegion);
clack_1.default.log.info('Using provided API key (CI mode - OAuth bypassed)');
const projectData = await fetchProjectDataWithApiKey(_options.apiKey, _options.cloudRegion);
return {
host,
projectApiKey: projectData.api_token, // Project API key for SDK config
accessToken: _options.apiKey, // Personal API key for LLM gateway
projectId: projectData.id,
};
}
const { host, projectApiKey, accessToken, projectId } = await (0, telemetry_1.traceStep)('login', () => askForWizardLogin({
cloudRegion: _options.cloudRegion,
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 {
accessToken,
host: host || constants_1.DEFAULT_HOST_URL,
projectApiKey: projectApiKey || constants_1.DUMMY_PROJECT_API_KEY,
projectId,
};
}
/**
* Fetch project data using a personal API key (for CI mode)
*/
async function fetchProjectDataWithApiKey(apiKey, region) {
const cloudUrl = (0, urls_1.getCloudUrlFromRegion)(region);
const userData = await (0, api_1.fetchUserData)(apiKey, cloudUrl);
const projectId = userData.team?.id;
if (!projectId) {
throw new Error('Could not determine project ID from API key. Please ensure your API key has access to a project in this cloud region.');
}
const projectData = await (0, api_1.fetchProjectData)(apiKey, projectId, cloudUrl);
return {
api_token: projectData.api_token,
id: projectId,
};
}
async function askForWizardLogin(options) {
const tokenResponse = await (0, oauth_1.performOAuthFlow)({
cloudRegion: options.cloudRegion,
scopes: [
'user:read',
'project:read',
'introspection',
'llm_gateway:read',
'dashboard:write',
'insight:write',
],
signup: options.signup,
});
const projectId = tokenResponse.scoped_teams?.[0];
if (projectId === undefined) {
const error = new Error('No project access granted. Please authorize with project-level access.');
analytics_1.analytics.captureException(error, {
step: 'wizard_login',
has_scoped_teams: !!tokenResponse.scoped_teams,
});
clack_1.default.log.error(error.message);
await abort();
}
const cloudUrl = (0, urls_1.getCloudUrlFromRegion)(options.cloudRegion);
const host = (0, urls_1.getHostFromRegion)(options.cloudRegion);
const projectData = await (0, api_1.fetchProjectData)(tokenResponse.access_token, projectId, cloudUrl);
const userData = await (0, api_1.fetchUserData)(tokenResponse.access_token, cloudUrl);
const data = {
accessToken: tokenResponse.access_token,
projectApiKey: projectData.api_token,
host,
distinctId: userData.distinct_id,
projectId: projectId,
};
clack_1.default.log.success(`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 () => {
// CI mode: auto-consent to AI
const aiConsent = options.default || options.ci
? 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