Skip to content

Commit 5b9e357

Browse files
committed
simplify install command to single confirmation with checkmarks and full paths
1 parent ae78764 commit 5b9e357

File tree

5 files changed

+146
-184
lines changed

5 files changed

+146
-184
lines changed

pkgs/cli/src/commands/install/copy-migrations.ts

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ export async function copyMigrations({
233233

234234
// If no files to copy, show message and return false (no changes made)
235235
if (filesToCopy.length === 0) {
236-
log.success(`All ${skippedFiles.length} pgflow migrations are already in place`);
236+
log.success('Migrations already up to date');
237237
return false;
238238
}
239239

@@ -247,32 +247,28 @@ export async function copyMigrations({
247247
file.destination = `${baseTimestamp}_${file.source}`;
248248
});
249249

250-
// Build summary message with explanation - show all migrations
251-
const migrationLines = filesToCopy.map((file) => {
252-
return ` ${chalk.bold(file.source)}`;
253-
});
254-
255-
const summaryMsg = [
256-
`Add to ${chalk.cyan('migrations/')} ${chalk.dim('(database schema for workflow engine)')}:`,
257-
'',
258-
...migrationLines,
259-
].join('\n');
250+
// Show preview and ask for confirmation only when not auto-confirming
251+
if (!autoConfirm) {
252+
const migrationLines = filesToCopy.map((file) => {
253+
return ` ${chalk.bold(file.source)}`;
254+
});
260255

261-
log.info(summaryMsg);
256+
const summaryMsg = [
257+
`Add to ${chalk.cyan('migrations/')} ${chalk.dim('(database schema for workflow engine)')}:`,
258+
'',
259+
...migrationLines,
260+
].join('\n');
262261

263-
let shouldContinue = autoConfirm;
262+
log.info(summaryMsg);
264263

265-
if (!autoConfirm) {
266264
const confirmResult = await confirm({
267265
message: `Add ${filesToCopy.length} migration${filesToCopy.length !== 1 ? 's' : ''}?`,
268266
});
269267

270-
shouldContinue = confirmResult === true;
271-
}
272-
273-
if (!shouldContinue) {
274-
log.warn('Migration installation skipped');
275-
return false;
268+
if (confirmResult !== true) {
269+
log.warn('Migration installation skipped');
270+
return false;
271+
}
276272
}
277273

278274
// Install migrations with new filenames
@@ -283,12 +279,7 @@ export async function copyMigrations({
283279
fs.copyFileSync(sourcePath1, destinationPath);
284280
}
285281

286-
const successMsg = [
287-
`Installed ${filesToCopy.length} migration${filesToCopy.length !== 1 ? 's' : ''}`,
288-
` ${chalk.dim('Learn more:')} ${chalk.blue.underline('https://pgflow.dev/concepts/data-model/')}`,
289-
].join('\n');
290-
291-
log.success(successMsg);
282+
log.success(`Installed ${filesToCopy.length} migration${filesToCopy.length !== 1 ? 's' : ''}`);
292283

293284
return true; // Return true to indicate migrations were copied
294285
}

pkgs/cli/src/commands/install/create-edge-function.ts

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -59,40 +59,28 @@ export async function createEdgeFunction({
5959

6060
// If all files exist, return success
6161
if (filesToCreate.length === 0) {
62-
const detailedMsg = [
63-
'ControlPlane edge function files are already in place:',
64-
` ${chalk.bold(relativeIndexPath)}`,
65-
` ${chalk.bold(relativeDenoJsonPath)}`,
66-
].join('\n');
67-
68-
log.success(detailedMsg);
69-
62+
log.success('Control Plane already up to date');
7063
return false;
7164
}
7265

73-
// Show what will be created with explanation
74-
const summaryMsg = [
75-
`Create ${chalk.cyan('functions/pgflow/')} ${chalk.dim('(Control Plane for flow registration and compilation)')}:`,
76-
'',
77-
...filesToCreate.map((file) => ` ${chalk.bold(path.basename(file.relativePath))}`),
78-
].join('\n');
79-
80-
log.info(summaryMsg);
66+
// Show preview and ask for confirmation only when not auto-confirming
67+
if (!autoConfirm) {
68+
const summaryMsg = [
69+
`Create ${chalk.cyan('functions/pgflow/')} ${chalk.dim('(Control Plane for flow registration and compilation)')}:`,
70+
'',
71+
...filesToCreate.map((file) => ` ${chalk.bold(path.basename(file.relativePath))}`),
72+
].join('\n');
8173

82-
// Get confirmation
83-
let shouldContinue = autoConfirm;
74+
log.info(summaryMsg);
8475

85-
if (!autoConfirm) {
8676
const confirmResult = await confirm({
8777
message: `Create functions/pgflow/?`,
8878
});
8979

90-
shouldContinue = confirmResult === true;
91-
}
92-
93-
if (!shouldContinue) {
94-
log.warn('Control Plane installation skipped');
95-
return false;
80+
if (confirmResult !== true) {
81+
log.warn('Control Plane installation skipped');
82+
return false;
83+
}
9684
}
9785

9886
// Create the directory if it doesn't exist
@@ -109,12 +97,7 @@ export async function createEdgeFunction({
10997
fs.writeFileSync(denoJsonPath, DENO_JSON_TEMPLATE(getVersion()));
11098
}
11199

112-
const successMsg = [
113-
`Control Plane installed`,
114-
` ${chalk.dim('Learn more:')} ${chalk.blue.underline('https://pgflow.dev/concepts/compilation/')}`,
115-
].join('\n');
116-
117-
log.success(successMsg);
100+
log.success('Control Plane installed');
118101

119102
return true;
120103
}
Lines changed: 65 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { type Command } from 'commander';
2-
import { intro, group, cancel, outro } from '@clack/prompts';
2+
import { intro, log, confirm, cancel, outro } from '@clack/prompts';
33
import chalk from 'chalk';
44
import { copyMigrations } from './copy-migrations.js';
55
import { updateConfigToml } from './update-config-toml.js';
@@ -16,84 +16,81 @@ export default (program: Command) => {
1616
.action(async (options) => {
1717
intro('Installing pgflow in your Supabase project');
1818

19-
// Use the group feature to organize installation steps
20-
const results = await group(
21-
{
22-
// Step 1: Determine Supabase path
23-
supabasePath: () =>
24-
supabasePathPrompt({ supabasePath: options.supabasePath }),
25-
26-
// Step 2: Update config.toml
27-
configUpdate: async ({ results: { supabasePath } }) => {
28-
if (!supabasePath) return false;
29-
30-
return await updateConfigToml({
31-
supabasePath,
32-
autoConfirm: options.yes,
33-
});
34-
},
35-
36-
// Step 3: Copy migrations
37-
migrations: async ({ results: { supabasePath } }) => {
38-
if (!supabasePath) return false;
39-
40-
return await copyMigrations({
41-
supabasePath,
42-
autoConfirm: options.yes,
43-
});
44-
},
45-
46-
// Step 4: Create ControlPlane edge function
47-
edgeFunction: async ({ results: { supabasePath } }) => {
48-
if (!supabasePath) return false;
49-
50-
return await createEdgeFunction({
51-
supabasePath,
52-
autoConfirm: options.yes,
53-
});
54-
},
55-
56-
// Step 5: Update environment variables
57-
envFile: async ({ results: { supabasePath } }) => {
58-
if (!supabasePath) return false;
59-
60-
return await updateEnvFile({
61-
supabasePath,
62-
autoConfirm: options.yes,
63-
});
64-
},
65-
},
66-
{
67-
// Handle cancellation
68-
onCancel: () => {
69-
cancel('Installation cancelled');
70-
process.exit(1);
71-
},
19+
// Step 1: Get supabase path
20+
const supabasePathResult = await supabasePathPrompt({
21+
supabasePath: options.supabasePath,
22+
});
23+
24+
if (!supabasePathResult || typeof supabasePathResult === 'symbol') {
25+
cancel('Installation cancelled - valid Supabase path is required');
26+
process.exit(1);
27+
}
28+
29+
const supabasePath = supabasePathResult;
30+
31+
// Step 2: Show summary and get single confirmation
32+
const summaryMsg = [
33+
'This will:',
34+
'',
35+
` • Update ${chalk.cyan('supabase/config.toml')} ${chalk.dim('(enable pooler, per_worker runtime)')}`,
36+
` • Add pgflow migrations to ${chalk.cyan('supabase/migrations/')}`,
37+
` • Create Control Plane in ${chalk.cyan('supabase/functions/pgflow/')}`,
38+
` • Configure ${chalk.cyan('supabase/functions/.env')}`,
39+
'',
40+
` ${chalk.green('✓ Safe to re-run - completed steps will be skipped')}`,
41+
].join('\n');
42+
43+
log.info(summaryMsg);
44+
45+
let shouldProceed = options.yes;
46+
47+
if (!options.yes) {
48+
const confirmResult = await confirm({
49+
message: 'Proceed?',
50+
});
51+
52+
if (confirmResult !== true) {
53+
cancel('Installation cancelled');
54+
process.exit(1);
7255
}
73-
);
7456

75-
// Extract the results from the group operation
76-
const supabasePath = results.supabasePath;
77-
const configUpdate = results.configUpdate;
78-
const migrations = results.migrations;
79-
const edgeFunction = results.edgeFunction;
80-
const envFile = results.envFile;
57+
shouldProceed = true;
58+
}
8159

82-
// Exit if supabasePath is null (validation failed or user cancelled)
83-
if (!supabasePath) {
84-
cancel('Installation cancelled - valid Supabase path is required');
60+
if (!shouldProceed) {
61+
cancel('Installation cancelled');
8562
process.exit(1);
8663
}
8764

88-
// Show completion message
65+
// Step 3: Run all installation steps with autoConfirm
66+
const configUpdate = await updateConfigToml({
67+
supabasePath,
68+
autoConfirm: true,
69+
});
70+
71+
const migrations = await copyMigrations({
72+
supabasePath,
73+
autoConfirm: true,
74+
});
75+
76+
const edgeFunction = await createEdgeFunction({
77+
supabasePath,
78+
autoConfirm: true,
79+
});
80+
81+
const envFile = await updateEnvFile({
82+
supabasePath,
83+
autoConfirm: true,
84+
});
85+
86+
// Step 4: Show completion message
8987
const outroMessages: string[] = [];
9088

91-
// Always start with a bolded acknowledgement
9289
if (migrations || configUpdate || edgeFunction || envFile) {
93-
outroMessages.push(chalk.bold('Installation complete!'));
90+
outroMessages.push(chalk.green.bold('Installation complete!'));
9491
} else {
9592
outroMessages.push(
96-
chalk.bold('pgflow is already installed - no changes needed!')
93+
chalk.green.bold('pgflow is already installed - no changes needed!')
9794
);
9895
}
9996

@@ -121,7 +118,6 @@ export default (program: Command) => {
121118
` ${stepNumber}. Create your first flow: ${chalk.blue.underline('https://pgflow.dev/getting-started/create-first-flow/')}`
122119
);
123120

124-
// Single outro for all paths
125121
outro(outroMessages.join('\n'));
126122
});
127123
};

pkgs/cli/src/commands/install/update-config-toml.ts

Lines changed: 35 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -73,52 +73,49 @@ export async function updateConfigToml({
7373
currentSettings.edgeRuntimePolicy !== 'per_worker';
7474

7575
if (!needsChanges) {
76-
log.success('Supabase configuration is already set up for pgflow');
76+
log.success('Configuration already up to date');
7777
return false;
7878
}
7979

80-
const changes: string[] = [];
81-
82-
// Connection pooler changes
83-
const poolerChanges: string[] = [];
84-
if (currentSettings.poolerEnabled !== true) {
85-
poolerChanges.push(`enabled = ${currentSettings.poolerEnabled} ${chalk.dim('->')} ${chalk.green('true')}`);
86-
}
87-
if (currentSettings.poolMode !== 'transaction') {
88-
poolerChanges.push(`pool_mode = "${currentSettings.poolMode}" ${chalk.dim('->')} ${chalk.green('"transaction"')}`);
89-
}
90-
if (poolerChanges.length > 0) {
91-
changes.push(` ${chalk.bold('[db.pooler]')} ${chalk.dim('(required for pgflow worker)')}`);
92-
poolerChanges.forEach(change => changes.push(` ${change}`));
93-
}
94-
95-
// Edge runtime changes
96-
if (currentSettings.edgeRuntimePolicy !== 'per_worker') {
97-
changes.push(` ${chalk.bold('[edge_runtime]')} ${chalk.dim('(required for long-running tasks)')}`);
98-
changes.push(` policy = "${currentSettings.edgeRuntimePolicy}" ${chalk.dim('->')} ${chalk.green('"per_worker"')}`);
99-
}
100-
101-
const summaryMsg = [
102-
`Update ${chalk.cyan('config.toml')}:`,
103-
'',
104-
...changes,
105-
].join('\n');
106-
107-
log.info(summaryMsg);
108-
109-
let shouldContinue = autoConfirm;
110-
80+
// Show preview and ask for confirmation only when not auto-confirming
11181
if (!autoConfirm) {
82+
const changes: string[] = [];
83+
84+
// Connection pooler changes
85+
const poolerChanges: string[] = [];
86+
if (currentSettings.poolerEnabled !== true) {
87+
poolerChanges.push(`enabled = ${currentSettings.poolerEnabled} ${chalk.dim('->')} ${chalk.green('true')}`);
88+
}
89+
if (currentSettings.poolMode !== 'transaction') {
90+
poolerChanges.push(`pool_mode = "${currentSettings.poolMode}" ${chalk.dim('->')} ${chalk.green('"transaction"')}`);
91+
}
92+
if (poolerChanges.length > 0) {
93+
changes.push(` ${chalk.bold('[db.pooler]')} ${chalk.dim('(required for pgflow worker)')}`);
94+
poolerChanges.forEach(change => changes.push(` ${change}`));
95+
}
96+
97+
// Edge runtime changes
98+
if (currentSettings.edgeRuntimePolicy !== 'per_worker') {
99+
changes.push(` ${chalk.bold('[edge_runtime]')} ${chalk.dim('(required for long-running tasks)')}`);
100+
changes.push(` policy = "${currentSettings.edgeRuntimePolicy}" ${chalk.dim('->')} ${chalk.green('"per_worker"')}`);
101+
}
102+
103+
const summaryMsg = [
104+
`Update ${chalk.cyan('config.toml')}:`,
105+
'',
106+
...changes,
107+
].join('\n');
108+
109+
log.info(summaryMsg);
110+
112111
const confirmResult = await confirm({
113112
message: `Update config.toml? (backup will be created)`,
114113
});
115114

116-
shouldContinue = confirmResult === true;
117-
}
118-
119-
if (!shouldContinue) {
120-
log.warn('Configuration update skipped');
121-
return false;
115+
if (confirmResult !== true) {
116+
log.warn('Configuration update skipped');
117+
return false;
118+
}
122119
}
123120

124121
// Update Supabase configuration

0 commit comments

Comments
 (0)