From b7390c479539a59decb78a5fa60bdcd52dda4fcb Mon Sep 17 00:00:00 2001 From: Wojtek Majewski Date: Fri, 19 Dec 2025 13:34:04 +0100 Subject: [PATCH] add Installer.run() API for simplified Lovable integration --- pkgs/edge-worker/deno.lock | 3 +- pkgs/edge-worker/src/index.ts | 3 + pkgs/edge-worker/src/installer/index.ts | 8 + pkgs/edge-worker/src/installer/server.ts | 176 ++++++++++ pkgs/edge-worker/src/installer/types.ts | 13 + .../tests/unit/installer/server.test.ts | 118 +++++++ .../lovable/LovableInstallPrompt.astro | 319 ++++++++++++++++++ .../tutorials/lovable/01-install-pgflow.mdx | 89 +++-- .../lovable/02-install-migrations.mdx | 228 ------------- .../content/docs/tutorials/lovable/index.mdx | 26 +- 10 files changed, 687 insertions(+), 296 deletions(-) create mode 100644 pkgs/edge-worker/src/installer/index.ts create mode 100644 pkgs/edge-worker/src/installer/server.ts create mode 100644 pkgs/edge-worker/src/installer/types.ts create mode 100644 pkgs/edge-worker/tests/unit/installer/server.test.ts create mode 100644 pkgs/website/src/components/lovable/LovableInstallPrompt.astro delete mode 100644 pkgs/website/src/content/docs/tutorials/lovable/02-install-migrations.mdx diff --git a/pkgs/edge-worker/deno.lock b/pkgs/edge-worker/deno.lock index 85814d93f..ee2d19e1c 100644 --- a/pkgs/edge-worker/deno.lock +++ b/pkgs/edge-worker/deno.lock @@ -15,7 +15,8 @@ "jsr:@std/io@0.225": "0.225.0", "jsr:@std/io@0.225.0": "0.225.0", "jsr:@std/testing@0.224": "0.224.0", - "npm:@supabase/supabase-js@^2.39.0": "2.86.0" + "npm:@supabase/supabase-js@^2.39.0": "2.86.0", + "npm:@types/node@*": "22.5.4" }, "jsr": { "@deno-library/progress@1.5.1": { diff --git a/pkgs/edge-worker/src/index.ts b/pkgs/edge-worker/src/index.ts index b75344c97..01b3d39de 100644 --- a/pkgs/edge-worker/src/index.ts +++ b/pkgs/edge-worker/src/index.ts @@ -9,6 +9,9 @@ export { FlowWorkerLifecycle } from './flow/FlowWorkerLifecycle.js'; // Export ControlPlane for HTTP-based flow compilation export { ControlPlane } from './control-plane/index.js'; +// Export Installer for no-CLI platforms (e.g., Lovable) +export { Installer } from './installer/index.js'; + // Export platform adapters export * from './platform/index.js'; diff --git a/pkgs/edge-worker/src/installer/index.ts b/pkgs/edge-worker/src/installer/index.ts new file mode 100644 index 000000000..8dc0fe490 --- /dev/null +++ b/pkgs/edge-worker/src/installer/index.ts @@ -0,0 +1,8 @@ +import { createInstallerHandler } from './server.ts'; + +export const Installer = { + run: (token: string) => { + const handler = createInstallerHandler(token); + Deno.serve({}, handler); + }, +}; diff --git a/pkgs/edge-worker/src/installer/server.ts b/pkgs/edge-worker/src/installer/server.ts new file mode 100644 index 000000000..42d49ca32 --- /dev/null +++ b/pkgs/edge-worker/src/installer/server.ts @@ -0,0 +1,176 @@ +import type { InstallerResult, StepResult } from './types.ts'; +import postgres from 'postgres'; +import { MigrationRunner } from '../control-plane/migrations/index.ts'; +import { extractProjectId } from '../control-plane/server.ts'; + +// Dependency injection for testability +export interface InstallerDeps { + getEnv: (key: string) => string | undefined; +} + +const defaultDeps: InstallerDeps = { + getEnv: (key) => Deno.env.get(key), +}; + +export function createInstallerHandler( + expectedToken: string, + deps: InstallerDeps = defaultDeps +): (req: Request) => Promise { + return async (req: Request) => { + // Validate token from query params first (fail fast) + const url = new URL(req.url); + const token = url.searchParams.get('token'); + + if (token !== expectedToken) { + return jsonResponse( + { + success: false, + message: + 'Invalid or missing token. Use the exact URL from your Lovable prompt.', + }, + 401 + ); + } + + // Read env vars inside handler (not at module level) + const supabaseUrl = deps.getEnv('SUPABASE_URL'); + const serviceRoleKey = deps.getEnv('SUPABASE_SERVICE_ROLE_KEY'); + const dbUrl = deps.getEnv('SUPABASE_DB_URL'); + + if (!supabaseUrl || !serviceRoleKey) { + return jsonResponse( + { + success: false, + message: 'Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY', + }, + 500 + ); + } + + if (!dbUrl) { + return jsonResponse( + { + success: false, + message: 'Missing SUPABASE_DB_URL', + }, + 500 + ); + } + + console.log('pgflow installer starting...'); + + // Create database connection + const sql = postgres(dbUrl, { prepare: false }); + + let secrets: StepResult; + let migrations: StepResult; + + try { + // Step 1: Configure vault secrets + console.log('Configuring vault secrets...'); + secrets = await configureSecrets(sql, supabaseUrl, serviceRoleKey); + + if (!secrets.success) { + const result: InstallerResult = { + success: false, + secrets, + migrations: { + success: false, + status: 0, + error: 'Skipped - secrets failed', + }, + message: 'Failed to configure vault secrets.', + }; + return jsonResponse(result, 500); + } + + // Step 2: Run migrations + console.log('Running migrations...'); + migrations = await runMigrations(sql); + + const result: InstallerResult = { + success: secrets.success && migrations.success, + secrets, + migrations, + message: migrations.success + ? 'pgflow installed successfully! Vault secrets configured and migrations applied.' + : 'Secrets configured but migrations failed. Check the error details.', + }; + + console.log('Installer complete:', result.message); + return jsonResponse(result, result.success ? 200 : 500); + } finally { + await sql.end(); + } + }; +} + +/** + * Configure vault secrets for pgflow + */ +async function configureSecrets( + sql: postgres.Sql, + supabaseUrl: string, + serviceRoleKey: string +): Promise { + try { + const projectId = extractProjectId(supabaseUrl); + if (!projectId) { + return { + success: false, + status: 500, + error: 'Could not extract project ID from SUPABASE_URL', + }; + } + + // Upsert secrets (delete + create pattern) in single transaction + await sql.begin(async (tx) => { + await tx`DELETE FROM vault.secrets WHERE name = 'supabase_project_id'`; + await tx`SELECT vault.create_secret(${projectId}, 'supabase_project_id')`; + + await tx`DELETE FROM vault.secrets WHERE name = 'supabase_service_role_key'`; + await tx`SELECT vault.create_secret(${serviceRoleKey}, 'supabase_service_role_key')`; + }); + + return { + success: true, + status: 200, + data: { configured: ['supabase_project_id', 'supabase_service_role_key'] }, + }; + } catch (error) { + return { + success: false, + status: 500, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } +} + +/** + * Run pending migrations + */ +async function runMigrations(sql: postgres.Sql): Promise { + try { + const runner = new MigrationRunner(sql); + const result = await runner.up(); + + return { + success: result.success, + status: result.success ? 200 : 500, + data: result, + }; + } catch (error) { + return { + success: false, + status: 500, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } +} + +function jsonResponse(data: unknown, status: number): Response { + return new Response(JSON.stringify(data, null, 2), { + status, + headers: { 'Content-Type': 'application/json' }, + }); +} diff --git a/pkgs/edge-worker/src/installer/types.ts b/pkgs/edge-worker/src/installer/types.ts new file mode 100644 index 000000000..f17a41155 --- /dev/null +++ b/pkgs/edge-worker/src/installer/types.ts @@ -0,0 +1,13 @@ +export interface StepResult { + success: boolean; + status: number; + data?: unknown; + error?: string; +} + +export interface InstallerResult { + success: boolean; + secrets: StepResult; + migrations: StepResult; + message: string; +} diff --git a/pkgs/edge-worker/tests/unit/installer/server.test.ts b/pkgs/edge-worker/tests/unit/installer/server.test.ts new file mode 100644 index 000000000..89c993fea --- /dev/null +++ b/pkgs/edge-worker/tests/unit/installer/server.test.ts @@ -0,0 +1,118 @@ +import { assertEquals, assertMatch } from '@std/assert'; +import { + createInstallerHandler, + type InstallerDeps, +} from '../../../src/installer/server.ts'; + +// Helper to create mock dependencies +function createMockDeps(overrides?: Partial): InstallerDeps { + return { + getEnv: (key: string) => + ({ + SUPABASE_URL: 'https://test-project.supabase.co', + SUPABASE_SERVICE_ROLE_KEY: 'test-service-role-key', + SUPABASE_DB_URL: 'postgresql://postgres:postgres@localhost:54322/postgres', + })[key], + ...overrides, + }; +} + +// Helper to create a request with optional token +function createRequest(token?: string): Request { + const url = token + ? `http://localhost/pgflow-installer?token=${token}` + : 'http://localhost/pgflow-installer'; + return new Request(url); +} + +// ============================================================ +// Token validation tests +// ============================================================ + +Deno.test('Installer Handler - returns 401 when token missing', async () => { + const deps = createMockDeps(); + const handler = createInstallerHandler('expected-token', deps); + + const request = createRequest(); // no token + const response = await handler(request); + + assertEquals(response.status, 401); + const data = await response.json(); + assertEquals(data.success, false); + assertMatch(data.message, /Invalid or missing token/); +}); + +Deno.test('Installer Handler - returns 401 when token incorrect', async () => { + const deps = createMockDeps(); + const handler = createInstallerHandler('expected-token', deps); + + const request = createRequest('wrong-token'); + const response = await handler(request); + + assertEquals(response.status, 401); + const data = await response.json(); + assertEquals(data.success, false); + assertMatch(data.message, /Invalid or missing token/); +}); + +// ============================================================ +// Environment variable validation tests +// ============================================================ + +Deno.test('Installer Handler - returns 500 when SUPABASE_URL missing', async () => { + const deps = createMockDeps({ + getEnv: (key: string) => + ({ + SUPABASE_SERVICE_ROLE_KEY: 'test-key', + // SUPABASE_URL is undefined + })[key], + }); + const handler = createInstallerHandler('valid-token', deps); + + const request = createRequest('valid-token'); + const response = await handler(request); + + assertEquals(response.status, 500); + const data = await response.json(); + assertEquals(data.success, false); + assertMatch(data.message, /Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY/); +}); + +Deno.test('Installer Handler - returns 500 when SUPABASE_SERVICE_ROLE_KEY missing', async () => { + const deps = createMockDeps({ + getEnv: (key: string) => + ({ + SUPABASE_URL: 'https://test.supabase.co', + // SUPABASE_SERVICE_ROLE_KEY is undefined + })[key], + }); + const handler = createInstallerHandler('valid-token', deps); + + const request = createRequest('valid-token'); + const response = await handler(request); + + assertEquals(response.status, 500); + const data = await response.json(); + assertEquals(data.success, false); + assertMatch(data.message, /Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY/); +}); + +Deno.test('Installer Handler - returns 500 when SUPABASE_DB_URL missing', async () => { + const deps = createMockDeps({ + getEnv: (key: string) => + ({ + SUPABASE_URL: 'https://test.supabase.co', + SUPABASE_SERVICE_ROLE_KEY: 'test-key', + // SUPABASE_DB_URL is undefined + })[key], + }); + const handler = createInstallerHandler('valid-token', deps); + + const request = createRequest('valid-token'); + const response = await handler(request); + + assertEquals(response.status, 500); + const data = await response.json(); + assertEquals(data.success, false); + assertMatch(data.message, /Missing SUPABASE_DB_URL/); +}); diff --git a/pkgs/website/src/components/lovable/LovableInstallPrompt.astro b/pkgs/website/src/components/lovable/LovableInstallPrompt.astro new file mode 100644 index 000000000..793eda3cd --- /dev/null +++ b/pkgs/website/src/components/lovable/LovableInstallPrompt.astro @@ -0,0 +1,319 @@ +--- +/** + * LovableInstallPrompt component + * + * Renders the pgflow installation prompt for Lovable with client-side + * generated security tokens. Each page load generates: + * - A random function suffix (8 chars) to make the installer function name unpredictable + * - A random token (UUID) to authorize installer requests + * + * The installer connects directly to the database to run migrations and + * configure vault secrets - no Control Plane needed during installation. + */ +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; + +// Read version from edge-worker package.json at build time +const packageJsonPath = resolve(process.cwd(), '../edge-worker/package.json'); +const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); +const defaultEdgeWorkerVersion = packageJson.version; + +const promptText = `I need to install pgflow - a workflow engine for Supabase Edge Functions. + +**CRITICAL REQUIREMENTS - READ BEFORE STARTING:** + +1. **config.toml is REQUIRED** - You MUST create/update \`supabase/config.toml\` with \`verify_jwt = false\` for the installer function. Without this, the installer will fail with 401 errors. + +2. **Deno errors are expected** - You will see errors about "npm:postgres" in the preview. These are harmless and only affect Lovable's local type checker. Do NOT try to fix them. + +--- + +**STEP 1: Enable Lovable Cloud** + +If not already enabled, enable Lovable Cloud for this project now. +You will need the Supabase project reference to construct the installer link later. + +--- + +**STEP 2: Create the installer Edge Function** + +Create Edge Function \`supabase/functions/pgflow-installer-/index.ts\`: +\`\`\`ts +import { Installer } from 'jsr:@pgflow/edge-worker'; + +Installer.run(''); +\`\`\` + +--- + +**STEP 3: Configure supabase/config.toml (CRITICAL)** + +This step is REQUIRED. Without it, the installer will fail with 401 errors. + +Create or update \`supabase/config.toml\` with this EXACT entry: + +\`\`\`toml +[functions.pgflow-installer-] +verify_jwt = false +\`\`\` + +**Why this is safe:** The installer is a temporary function with a random name suffix and requires a secret token in the URL. It will be deleted immediately after use. + +--- + +**STEP 4: Verify files before deploying** + +Before proceeding, verify the files were created correctly. Check each item and show me a summary: + +- [checkmark or red X emoji] \`supabase/functions/pgflow-installer-/index.ts\` exists and imports Installer +- [checkmark or red X emoji] \`supabase/config.toml\` has \`[functions.pgflow-installer-]\` with \`verify_jwt = false\` + +If ANY item has a red X, fix it before continuing. ALL items must be green checkmarks. + +--- + +**STEP 5: Deploy the Edge Function** + +Deploy the installer edge function to Supabase now. +Wait for deployment to complete successfully before proceeding. +Ignore any Deno preview errors - the deployment will work. + +--- + +**STEP 6: Show me the installer link** + +After deployment succeeds, construct and show me a clickable link: +\`https://[PROJECT_REF].supabase.co/functions/v1/pgflow-installer-?token=\` + +Replace [PROJECT_REF] with the actual Supabase project reference. + +--- + +**STEP 7: Wait for me to paste the result** + +I will click the link, copy the JSON response, and paste it here. + +Success response looks like: +\`\`\`json +{ + "success": true, + "secrets": { "success": true, "status": 200, "data": {...} }, + "migrations": { "success": true, "status": 200, "data": {...} }, + "message": "pgflow installed successfully!" +} +\`\`\` + +Error response looks like: +\`\`\`json +{ + "success": false, + "secrets": { "success": false, "status": 500, "error": "..." }, + "message": "Failed to configure vault secrets..." +} +\`\`\` + +--- + +**STEP 8: Interpret the result** + +When I paste the JSON: +- If \`"success": true\` - confirm pgflow is installed and ready +- If \`"success": false\` - explain what went wrong based on the error messages + +--- + +**STEP 9: Clean up the installer** + +After confirming success (or discussing errors), delete the temporary installer: +- Remove \`supabase/functions/pgflow-installer-/\` folder +- Remove the \`[functions.pgflow-installer-]\` section from \`config.toml\` + +This removes the temporary unauthenticated endpoint.`; +--- + +
+
+
+ +
+

+ Security tokens generated: + Function suffix: | + Auth token: + +

+
+ + + + diff --git a/pkgs/website/src/content/docs/tutorials/lovable/01-install-pgflow.mdx b/pkgs/website/src/content/docs/tutorials/lovable/01-install-pgflow.mdx index 724ca91b3..332b1d162 100644 --- a/pkgs/website/src/content/docs/tutorials/lovable/01-install-pgflow.mdx +++ b/pkgs/website/src/content/docs/tutorials/lovable/01-install-pgflow.mdx @@ -1,73 +1,66 @@ --- -title: "Step 1: Install Control Plane" -description: Create the pgflow Edge Function that manages workflow compilation and execution +title: "Install pgflow" +description: Install pgflow in your Lovable project with a single prompt sidebar: order: 10 --- import { Aside, Steps } from '@astrojs/starlight/components'; -import { FileTree } from '@astrojs/starlight/components'; +import LovableInstallPrompt from '@/components/lovable/LovableInstallPrompt.astro'; -The first step is to install the pgflow Control Plane - an Edge Function that manages your workflow definitions and compilation. +Install pgflow in your Lovable project using a single prompt. This runs database migrations and configures vault secrets needed for pgflow to work. -## What gets created +## Installation prompt -After pasting the prompt below, Lovable will create: +Copy and paste this entire prompt into Lovable: - -- supabase/ - - flows/ - - index.ts (empty - you'll add flows here later) - - functions/ - - pgflow/ - - index.ts (the Control Plane function) - + -## Installation prompt +## What to expect -Copy and paste this entire prompt into Lovable: + +1. **Paste the prompt** into Lovable's chat +2. **Enable Cloud** when Lovable prompts (if not already enabled) +3. **Wait for file creation** - Lovable creates the installer Edge Function +4. **Verify the checklist** - Lovable shows a verification checklist +5. **Wait for deployment** - Lovable deploys the installer to Supabase +6. **Click the installer link** - Lovable shows you a link to run the installation +7. **Paste the result** - Copy the JSON response and paste it back +8. **Confirm cleanup** - Lovable deletes the temporary installer + -````text -I need to install pgflow - a workflow engine for Supabase Edge Functions. + -Please create the following files: +## Troubleshooting -1. Create folder `supabase/flows/` and file `supabase/flows/index.ts` with this content: -```ts -// Flow definitions will be added here -// Example: export { default as MyFlow } from './my-flow.ts'; -``` +### "Missing SUPABASE_DB_URL" -2. Create Edge Function `supabase/functions/pgflow/index.ts` with this exact content: -```ts -import { ControlPlane } from 'jsr:@pgflow/edge-worker'; -import * as flows from '../../flows/index.ts'; +This environment variable should be automatically available in Supabase Edge Functions. If you see this error, try redeploying the installer. -ControlPlane.serve(flows); -``` +### "401" or "Invalid JWT" error -After creating these files, confirm that both were created successfully. -```` +Ensure the `config.toml` has `verify_jwt = false` for the `pgflow-installer-*` function. The prompt includes this, but you can verify manually. -## What to expect +### "Migrations failed" - -1. **Paste the prompt** into Lovable's chat -2. **Wait for confirmation** that the files were created -3. **Verify the structure** - Lovable should confirm both the `flows` folder and `pgflow` function exist - +Check the `migrations.data` field in the response for specific error messages. Common issues: +- Missing database permissions +- Conflicting table names +- Network timeout (try clicking the link again) - +Ensure the Edge Function was deployed successfully. You may need to ask Lovable to redeploy. + +## Next steps -## Next step +Once pgflow is installed, you can ask Lovable to: -Once Lovable confirms the files are created, proceed to run the database migrations: +- **Create the Control Plane** - Create `supabase/functions/pgflow/index.ts` with `ControlPlane.serve(flows)` +- **Create workflows** - Define flow definitions in `supabase/flows/` +- **Add task functions** - Build handlers that call AI APIs, process data, etc. +- **Start workflow runs** - Trigger flows from your app's frontend -[Step 2: Run Migrations →](/tutorials/lovable/02-install-migrations/) +Check out the [AI Web Scraper tutorial](/tutorials/ai-web-scraper/) for an example of building a complete workflow. diff --git a/pkgs/website/src/content/docs/tutorials/lovable/02-install-migrations.mdx b/pkgs/website/src/content/docs/tutorials/lovable/02-install-migrations.mdx deleted file mode 100644 index 9e53a0124..000000000 --- a/pkgs/website/src/content/docs/tutorials/lovable/02-install-migrations.mdx +++ /dev/null @@ -1,228 +0,0 @@ ---- -title: "Step 2: Run Migrations" -description: Apply pgflow database migrations and configure vault secrets using a temporary installer -sidebar: - order: 20 ---- - -import { Aside, Steps } from '@astrojs/starlight/components'; - -This step creates a temporary Edge Function that securely runs pgflow migrations and configures vault secrets. The installer uses your project's service role key internally - it's never exposed to your frontend. - - - -## How it works - - -1. You paste a prompt that creates a temporary installer Edge Function -2. Lovable shows you a link to invoke the function -3. You click the link - the installer runs migrations and configures secrets -4. You copy the JSON result and paste it back to Lovable -5. Lovable confirms success and deletes the temporary installer - - -## Installation prompt - -Copy and paste this entire prompt into Lovable: - -````text -I need to run pgflow database migrations. This requires a temporary installer Edge Function. - -**STEP 1: Create the installer** - -Create Edge Function `supabase/functions/pgflow-installer/index.ts` with this exact content: - -```ts -const SUPABASE_URL = Deno.env.get('SUPABASE_URL')!; -const SERVICE_ROLE_KEY = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; - -interface StepResult { - success: boolean; - status: number; - data?: unknown; - error?: string; -} - -interface InstallerResult { - success: boolean; - secrets: StepResult; - migrations: StepResult; - message: string; -} - -async function callControlPlane( - endpoint: string, - method: 'GET' | 'POST' -): Promise { - try { - const response = await fetch(`${SUPABASE_URL}/functions/v1/pgflow${endpoint}`, { - method, - headers: { - 'Authorization': `Bearer ${SERVICE_ROLE_KEY}`, - 'Content-Type': 'application/json', - }, - }); - - const data = await response.json(); - return { - success: response.ok, - status: response.status, - data, - }; - } catch (error) { - return { - success: false, - status: 0, - error: error instanceof Error ? error.message : 'Unknown error', - }; - } -} - -Deno.serve(async (_req: Request) => { - console.log('pgflow installer starting...'); - - // Step 1: Configure vault secrets - console.log('Configuring vault secrets...'); - const secrets = await callControlPlane('/secrets/configure', 'POST'); - - if (!secrets.success) { - const result: InstallerResult = { - success: false, - secrets, - migrations: { success: false, status: 0, error: 'Skipped - secrets failed' }, - message: 'Failed to configure vault secrets. Check the pgflow Control Plane is deployed.', - }; - return new Response(JSON.stringify(result, null, 2), { - status: 500, - headers: { 'Content-Type': 'application/json' }, - }); - } - - // Step 2: Run migrations - console.log('Running migrations...'); - const migrations = await callControlPlane('/migrations/up', 'POST'); - - const result: InstallerResult = { - success: secrets.success && migrations.success, - secrets, - migrations, - message: migrations.success - ? 'pgflow installed successfully! Vault secrets configured and migrations applied.' - : 'Secrets configured but migrations failed. Check the error details.', - }; - - console.log('Installer complete:', result.message); - - return new Response(JSON.stringify(result, null, 2), { - status: result.success ? 200 : 500, - headers: { 'Content-Type': 'application/json' }, - }); -}); -``` - -**STEP 2: Configure to skip JWT verification** - -Add to `supabase/config.toml`: - -```toml -[functions.pgflow-installer] -verify_jwt = false -``` - -**STEP 3: After the function is deployed, show me a link to invoke it** - -The link format is: `https://[PROJECT_REF].supabase.co/functions/v1/pgflow-installer` - -Present this as a clickable link so I can invoke the installer. - -**STEP 4: Wait for me to paste the result** - -After I click the link, I will copy the JSON response and paste it here. - -The response will look like this if successful: -```json -{ - "success": true, - "secrets": { "success": true, "status": 200, "data": {...} }, - "migrations": { "success": true, "status": 200, "data": {...} }, - "message": "pgflow installed successfully! Vault secrets configured and migrations applied." -} -``` - -Or like this if there's an error: -```json -{ - "success": false, - "secrets": { "success": false, "status": 500, "error": "..." }, - "migrations": {...}, - "message": "Failed to configure vault secrets..." -} -``` - -**STEP 5: Interpret the result** - -When I paste the JSON result: -- If `"success": true` - confirm that pgflow is installed and ready to use -- If `"success": false` - explain what went wrong based on the error messages - -**STEP 6: Clean up** - -After confirming success (or discussing the error), delete the temporary installer: -- Remove `supabase/functions/pgflow-installer/` folder -- Remove the `[functions.pgflow-installer]` section from `config.toml` - -This keeps the project clean and removes the unauthenticated endpoint. -```` - -## What to expect - -After pasting the prompt: - - -1. **Lovable creates the installer** - You'll see the function code being added -2. **Lovable shows you a link** - Click it to run the installer -3. **Copy the JSON result** - Select all the JSON output from the browser -4. **Paste it back to Lovable** - Lovable interprets whether it succeeded -5. **Lovable cleans up** - The temporary function is deleted - - - - -## Troubleshooting - -### "Failed to configure vault secrets" - -This usually means the pgflow Control Plane function isn't deployed yet. Make sure you completed [Step 1: Install Control Plane](/tutorials/lovable/01-install-pgflow/) first. - -### "Migrations failed" - -Check the `migrations.data` field in the response for specific error messages. Common issues: -- Missing database permissions -- Conflicting table names -- Network timeout (try again) - -### The link doesn't work - -Ensure the function was deployed to Supabase. You may need to ask Lovable to deploy the Edge Functions to your connected Supabase project. - -## Next steps - -Congratulations! pgflow is now installed in your Lovable project. You can now: - -- **Create workflows** - Ask Lovable to create flow definitions in `supabase/flows/` -- **Define tasks** - Build task functions that call AI APIs, process data, etc. -- **Run flows** - Start workflow executions from your app - -Check out the [AI Web Scraper tutorial](/tutorials/ai-web-scraper/) for an example of building a complete workflow. diff --git a/pkgs/website/src/content/docs/tutorials/lovable/index.mdx b/pkgs/website/src/content/docs/tutorials/lovable/index.mdx index cca3ff5bd..baea8920d 100644 --- a/pkgs/website/src/content/docs/tutorials/lovable/index.mdx +++ b/pkgs/website/src/content/docs/tutorials/lovable/index.mdx @@ -21,41 +21,29 @@ With pgflow integrated into your Lovable app, you can: - **Track progress** - Full observability into workflow state in your Postgres database - **Scale horizontally** - Edge Workers auto-scale with your Supabase project -## Installation overview +## Installation -Installing pgflow in a Lovable project requires a few prompts that you paste into Lovable's chat. The process is designed to be simple and safe. - - -1. **Install the Control Plane** - Create the pgflow Edge Function that manages flows -2. **Run Migrations** - Apply database migrations and configure secrets via a temporary installer - +Installing pgflow in a Lovable project takes just one prompt that you paste into Lovable's chat. The prompt creates a temporary installer that runs database migrations and configures vault secrets. -## Get started - - ## After installation Once pgflow is installed, you can ask Lovable to: -- Create new workflow definitions in `supabase/flows/` +- Create the Control Plane Edge Function (`supabase/functions/pgflow/`) +- Create workflow definitions in `supabase/flows/` - Define task functions that call AI APIs -- Compile flows and apply new migrations - Start workflow runs from your app's frontend Each workflow you create becomes a reliable, observable background process that Lovable can help you build and iterate on.