diff --git a/pkgs/cli/src/commands/install/copy-migrations.ts b/pkgs/cli/src/commands/install/copy-migrations.ts index d39c8efc4..04e5e2685 100644 --- a/pkgs/cli/src/commands/install/copy-migrations.ts +++ b/pkgs/cli/src/commands/install/copy-migrations.ts @@ -2,7 +2,7 @@ import fs from 'fs'; import path from 'path'; import { createRequire } from 'module'; import { fileURLToPath } from 'url'; -import { log, confirm, note } from '@clack/prompts'; +import { log, confirm } from '@clack/prompts'; import chalk from 'chalk'; // Get the directory name in ES modules @@ -231,38 +231,9 @@ export async function copyMigrations({ } } - // If no files to copy, show message with details and return false (no changes made) + // If no files to copy, show message and return false (no changes made) if (filesToCopy.length === 0) { - // Show success message - log.success('All pgflow migrations are already in place'); - - // Show details of already installed migrations - if (skippedFiles.length > 0) { - const detailedMsg = [ - 'Already installed migrations:', - ...skippedFiles.map((file) => { - // Find the matching existing file to show how it was installed - const matchingFile = existingFiles.find((existingFile) => - existingFile.includes(file) - ); - - if (matchingFile === file) { - // Installed with old direct method - return ` ${chalk.dim('•')} ${chalk.bold(file)}`; - } else { - // Installed with new timestamped method - const timestampPart = - matchingFile?.substring(0, matchingFile.indexOf(file) - 1) || ''; - return ` ${chalk.dim('•')} ${chalk.dim( - timestampPart + '_' - )}${chalk.bold(file)}`; - } - }), - ].join('\n'); - - note(detailedMsg, 'Existing pgflow Migrations'); - } - + log.success(`All ${skippedFiles.length} pgflow migrations are already in place`); return false; } @@ -276,48 +247,24 @@ export async function copyMigrations({ file.destination = `${baseTimestamp}_${file.source}`; }); - log.info( - `Found ${filesToCopy.length} migration${ - filesToCopy.length !== 1 ? 's' : '' - } to install` - ); - - // Prepare summary message with colored output - const summaryParts = []; - - if (filesToCopy.length > 0) { - summaryParts.push( - `${chalk.green('New migrations to install:')}\n${filesToCopy - .map((file) => { - // Extract the timestamp part from the new filename - const newTimestamp = file.destination.substring(0, 14); - // Format: dim timestamp + bright original name - return `${chalk.green('+')} ${file.source} → ${chalk.dim( - newTimestamp + '_' - )}${chalk.bold(file.source)}`; - }) - .join('\n')}` - ); - } + // Build summary message with explanation - show all migrations + const migrationLines = filesToCopy.map((file) => { + return ` ${chalk.bold(file.source)}`; + }); - if (skippedFiles.length > 0) { - summaryParts.push( - `${chalk.yellow('Already installed:')}\n${skippedFiles - .map((file) => `${chalk.yellow('•')} ${file}`) - .join('\n')}` - ); - } + const summaryMsg = [ + `Add to ${chalk.cyan('migrations/')} ${chalk.dim('(database schema for workflow engine)')}:`, + '', + ...migrationLines, + ].join('\n'); - // Show summary and ask for confirmation if not auto-confirming - note(summaryParts.join('\n\n'), 'pgflow Migrations'); + log.info(summaryMsg); let shouldContinue = autoConfirm; if (!autoConfirm) { const confirmResult = await confirm({ - message: `Install ${filesToCopy.length} new migration${ - filesToCopy.length !== 1 ? 's' : '' - }?`, + message: `Add ${filesToCopy.length} migration${filesToCopy.length !== 1 ? 's' : ''}?`, }); shouldContinue = confirmResult === true; @@ -336,18 +283,12 @@ export async function copyMigrations({ fs.copyFileSync(sourcePath1, destinationPath); } - // Show detailed success message with styled filenames - const detailedSuccessMsg = [ - `Installed ${filesToCopy.length} migration${ - filesToCopy.length !== 1 ? 's' : '' - } to your Supabase project:`, - ...filesToCopy.map((file) => { - const newTimestamp = file.destination.substring(0, 14); - return ` ${chalk.dim(newTimestamp + '_')}${chalk.bold(file.source)}`; - }), + const successMsg = [ + `Installed ${filesToCopy.length} migration${filesToCopy.length !== 1 ? 's' : ''}`, + ` ${chalk.dim('Learn more:')} ${chalk.blue.underline('https://pgflow.dev/concepts/data-model/')}`, ].join('\n'); - log.success(detailedSuccessMsg); + log.success(successMsg); return true; // Return true to indicate migrations were copied } diff --git a/pkgs/cli/src/commands/install/create-edge-function.ts b/pkgs/cli/src/commands/install/create-edge-function.ts index 7fe3d343f..11ddd6d47 100644 --- a/pkgs/cli/src/commands/install/create-edge-function.ts +++ b/pkgs/cli/src/commands/install/create-edge-function.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import { log, confirm, note } from '@clack/prompts'; +import { log, confirm } from '@clack/prompts'; import chalk from 'chalk'; import { getVersion } from '../../utils/get-version.js'; @@ -41,54 +41,57 @@ export async function createEdgeFunction({ const indexPath = path.join(pgflowFunctionDir, 'index.ts'); const denoJsonPath = path.join(pgflowFunctionDir, 'deno.json'); + // Relative paths for display + const relativeFunctionDir = 'supabase/functions/pgflow'; + const relativeIndexPath = `${relativeFunctionDir}/index.ts`; + const relativeDenoJsonPath = `${relativeFunctionDir}/deno.json`; + // Check what needs to be created - const filesToCreate: Array<{ path: string; name: string }> = []; + const filesToCreate: Array<{ path: string; relativePath: string }> = []; if (!fs.existsSync(indexPath)) { - filesToCreate.push({ path: indexPath, name: 'index.ts' }); + filesToCreate.push({ path: indexPath, relativePath: relativeIndexPath }); } if (!fs.existsSync(denoJsonPath)) { - filesToCreate.push({ path: denoJsonPath, name: 'deno.json' }); + filesToCreate.push({ path: denoJsonPath, relativePath: relativeDenoJsonPath }); } // If all files exist, return success if (filesToCreate.length === 0) { - log.success('ControlPlane edge function files are already in place'); - const detailedMsg = [ - 'Existing files:', - ` ${chalk.dim('•')} ${chalk.bold('supabase/functions/pgflow/index.ts')}`, - ` ${chalk.dim('•')} ${chalk.bold('supabase/functions/pgflow/deno.json')}`, + 'ControlPlane edge function files are already in place:', + ` ${chalk.bold(relativeIndexPath)}`, + ` ${chalk.bold(relativeDenoJsonPath)}`, ].join('\n'); - note(detailedMsg, 'ControlPlane Edge Function'); + log.success(detailedMsg); return false; } - // Show what will be created - log.info(`Found ${filesToCreate.length} file${filesToCreate.length !== 1 ? 's' : ''} to create`); - - const summaryParts = [`${chalk.green('Files to create:')}\n${filesToCreate - .map((file) => `${chalk.green('+')} ${file.name}`) - .join('\n')}`]; + // Show what will be created with explanation + const summaryMsg = [ + `Create ${chalk.cyan('functions/pgflow/')} ${chalk.dim('(Control Plane for flow registration and compilation)')}:`, + '', + ...filesToCreate.map((file) => ` ${chalk.bold(path.basename(file.relativePath))}`), + ].join('\n'); - note(summaryParts.join('\n'), 'ControlPlane Edge Function'); + log.info(summaryMsg); // Get confirmation let shouldContinue = autoConfirm; if (!autoConfirm) { const confirmResult = await confirm({ - message: `Create ${filesToCreate.length} file${filesToCreate.length !== 1 ? 's' : ''}?`, + message: `Create functions/pgflow/?`, }); shouldContinue = confirmResult === true; } if (!shouldContinue) { - log.warn('Edge function setup skipped'); + log.warn('Control Plane installation skipped'); return false; } @@ -106,13 +109,12 @@ export async function createEdgeFunction({ fs.writeFileSync(denoJsonPath, DENO_JSON_TEMPLATE(getVersion())); } - // Show success message - const detailedSuccessMsg = [ - `Created ${filesToCreate.length} file${filesToCreate.length !== 1 ? 's' : ''}:`, - ...filesToCreate.map((file) => ` ${chalk.bold(file.name)}`), + const successMsg = [ + `Control Plane installed`, + ` ${chalk.dim('Learn more:')} ${chalk.blue.underline('https://pgflow.dev/concepts/compilation/')}`, ].join('\n'); - log.success(detailedSuccessMsg); + log.success(successMsg); return true; } diff --git a/pkgs/cli/src/commands/install/index.ts b/pkgs/cli/src/commands/install/index.ts index c7b732e77..0b55288d9 100644 --- a/pkgs/cli/src/commands/install/index.ts +++ b/pkgs/cli/src/commands/install/index.ts @@ -86,46 +86,39 @@ export default (program: Command) => { } // Show completion message - const outroMessages = []; + const outroMessages: string[] = []; // Always start with a bolded acknowledgement if (migrations || configUpdate || edgeFunction || envFile) { - outroMessages.push(chalk.bold('pgflow setup completed successfully!')); + outroMessages.push(chalk.bold('Installation complete!')); } else { outroMessages.push( - chalk.bold( - 'pgflow is already properly configured - no changes needed!' - ) + chalk.bold('pgflow is already installed - no changes needed!') ); } - // Add a newline after the acknowledgement + // Add numbered next steps outroMessages.push(''); + outroMessages.push('Next steps:'); + + let stepNumber = 1; - // Add specific next steps if changes were made if (configUpdate || envFile) { outroMessages.push( - `- Restart your Supabase instance for configuration changes to take effect` + ` ${stepNumber}. Restart Supabase: ${chalk.cyan('supabase stop && supabase start')}` ); + stepNumber++; } if (migrations) { outroMessages.push( - `- Apply the migrations with: ${chalk.cyan('supabase migrations up')}` + ` ${stepNumber}. Apply migrations: ${chalk.cyan('supabase migrations up')}` ); - } - - // Always add documentation link with consistent formatting - if (outroMessages.length > 2) { - // If we have specific steps, add another newline - outroMessages.push(''); + stepNumber++; } outroMessages.push( - chalk.bold('Continue the setup:'), - chalk.blue.underline( - 'https://pgflow.dev/getting-started/create-first-flow/' - ) + ` ${stepNumber}. Create your first flow: ${chalk.blue.underline('https://pgflow.dev/getting-started/create-first-flow/')}` ); // Single outro for all paths diff --git a/pkgs/cli/src/commands/install/supabase-path-prompt.ts b/pkgs/cli/src/commands/install/supabase-path-prompt.ts index cc05fa708..57e301690 100644 --- a/pkgs/cli/src/commands/install/supabase-path-prompt.ts +++ b/pkgs/cli/src/commands/install/supabase-path-prompt.ts @@ -28,11 +28,6 @@ export async function supabasePathPrompt(options?: { supabasePath?: string }) { } } - // Always prompt for detected paths - don't skip - if (detectedPath) { - log.info(`Found Supabase project at: ${detectedPath}`); - } - const promptMessage = 'Where is your Supabase project located?'; const supabasePath = await text({ diff --git a/pkgs/cli/src/commands/install/update-config-toml.ts b/pkgs/cli/src/commands/install/update-config-toml.ts index aab8c8df7..5f9b75aeb 100644 --- a/pkgs/cli/src/commands/install/update-config-toml.ts +++ b/pkgs/cli/src/commands/install/update-config-toml.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import { log, confirm, note } from '@clack/prompts'; +import { log, confirm } from '@clack/prompts'; import * as TOML from '@decimalturn/toml-patch'; import chalk from 'chalk'; @@ -77,33 +77,40 @@ export async function updateConfigToml({ return false; } - const changes = []; + const changes: string[] = []; + // Connection pooler changes + const poolerChanges: string[] = []; if (currentSettings.poolerEnabled !== true) { - changes.push(`${chalk.bold('Enable connection pooler:')} -${chalk.red(`- enabled = ${currentSettings.poolerEnabled}`)} -${chalk.green('+ enabled = true')}`); + poolerChanges.push(`enabled = ${currentSettings.poolerEnabled} ${chalk.dim('->')} ${chalk.green('true')}`); } - if (currentSettings.poolMode !== 'transaction') { - changes.push(`${chalk.bold('Set pool mode to transaction:')} -${chalk.red(`- pool_mode = "${currentSettings.poolMode}"`)} -${chalk.green('+ pool_mode = "transaction"')}`); + poolerChanges.push(`pool_mode = "${currentSettings.poolMode}" ${chalk.dim('->')} ${chalk.green('"transaction"')}`); + } + if (poolerChanges.length > 0) { + changes.push(` ${chalk.bold('[db.pooler]')} ${chalk.dim('(required for pgflow worker)')}`); + poolerChanges.forEach(change => changes.push(` ${change}`)); } + // Edge runtime changes if (currentSettings.edgeRuntimePolicy !== 'per_worker') { - changes.push(`${chalk.bold('Set edge runtime policy:')} -${chalk.red(`- policy = "${currentSettings.edgeRuntimePolicy}"`)} -${chalk.green('+ policy = "per_worker"')}`); + changes.push(` ${chalk.bold('[edge_runtime]')} ${chalk.dim('(required for long-running tasks)')}`); + changes.push(` policy = "${currentSettings.edgeRuntimePolicy}" ${chalk.dim('->')} ${chalk.green('"per_worker"')}`); } - note(changes.join('\n\n'), 'Required Configuration Changes'); + const summaryMsg = [ + `Update ${chalk.cyan('config.toml')}:`, + '', + ...changes, + ].join('\n'); + + log.info(summaryMsg); let shouldContinue = autoConfirm; if (!autoConfirm) { const confirmResult = await confirm({ - message: `Update Supabase configuration? (a backup will be created)`, + message: `Update config.toml? (backup will be created)`, }); shouldContinue = confirmResult === true; @@ -149,7 +156,7 @@ ${chalk.green('+ policy = "per_worker"')}`); throw new Error(`Failed to write ${configPath}: ${errorMsg}`); } - log.success('Supabase configuration updated successfully'); + log.success('Configuration updated'); return true; } catch (error) { log.error( diff --git a/pkgs/cli/src/commands/install/update-env-file.ts b/pkgs/cli/src/commands/install/update-env-file.ts index 1590e53bf..915e479f5 100644 --- a/pkgs/cli/src/commands/install/update-env-file.ts +++ b/pkgs/cli/src/commands/install/update-env-file.ts @@ -1,6 +1,6 @@ import fs from 'fs'; import path from 'path'; -import { log, note, confirm } from '@clack/prompts'; +import { log, confirm } from '@clack/prompts'; import chalk from 'chalk'; /** @@ -47,16 +47,12 @@ export async function updateEnvFile({ // Prepare new content let newContent = currentContent; - // Build diff preview + // Check which variables need to be added const missingVars: Array<{ key: string; value: string }> = []; - const existingVars: Array = []; - // Check which variables need to be added for (const [key, value] of Object.entries(envVars)) { if (!newContent.includes(`${key}=`)) { missingVars.push({ key, value }); - } else { - existingVars.push(key); } } @@ -66,40 +62,23 @@ export async function updateEnvFile({ return false; } - log.info(`Found ${missingVars.length} variable${missingVars.length !== 1 ? 's' : ''} to add`); - - // Build diff preview - const diffPreview: Array = []; - - if (isNewFile) { - diffPreview.push(`${chalk.green('Creating new .env file with:')}`); - } else { - diffPreview.push(`${chalk.green('Adding to existing .env file:')}`); - } - - // Show variables to be added - for (const { key, value } of missingVars) { - diffPreview.push(`${chalk.green('+')} ${key}="${value}"`); - } - - // Show existing variables if any - if (existingVars.length > 0) { - diffPreview.push(''); - diffPreview.push(`${chalk.yellow('Already present:')}`); - for (const key of existingVars) { - diffPreview.push(`${chalk.yellow('•')} ${key}`); - } - } + // Build summary message with explanation + const summaryParts = [ + isNewFile + ? `Create ${chalk.cyan('functions/.env')} ${chalk.dim('(worker configuration)')}:` + : `Update ${chalk.cyan('functions/.env')} ${chalk.dim('(worker configuration)')}:`, + '', + ...missingVars.map(({ key, value }) => ` ${chalk.bold(key)}="${value}"`), + ]; - // Show the diff preview - note(diffPreview.join('\n'), 'Environment Variables'); + log.info(summaryParts.join('\n')); // Ask for confirmation if not auto-confirming let shouldContinue = autoConfirm; if (!autoConfirm) { const confirmResult = await confirm({ - message: `Update environment variables?`, + message: isNewFile ? `Create functions/.env?` : `Update functions/.env?`, }); shouldContinue = confirmResult === true; @@ -126,7 +105,7 @@ export async function updateEnvFile({ // Write the file if changes were made try { fs.writeFileSync(envFilePath, newContent); - log.success('Environment variables updated successfully'); + log.success('Environment variables configured'); return true; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error);