Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 18 additions & 77 deletions pkgs/cli/src/commands/install/copy-migrations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
Expand All @@ -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
}
50 changes: 26 additions & 24 deletions pkgs/cli/src/commands/install/create-edge-function.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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;
}

Expand All @@ -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;
}
31 changes: 12 additions & 19 deletions pkgs/cli/src/commands/install/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 0 additions & 5 deletions pkgs/cli/src/commands/install/supabase-path-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
37 changes: 22 additions & 15 deletions pkgs/cli/src/commands/install/update-config-toml.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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(
Expand Down
Loading