πŸ”βŒ˜K

Start typing to search docs.

Provider Integration

1.0.0

How the CLI orchestrates configuration providers and secrets.

Forge CLI Provider Integration (Phase 4)

This document details the implementation of provider selection and domain management features in the Forge CLI, completing Phase 4 of the App Factory platform implementation.

Overview

Phase 4 extends the Forge CLI to support:

  • Supabase provider integration
  • Domain configuration during app creation
  • Table strategy selection (prefixed vs app_id-based)
  • Scope management (personal vs customer apps)
  • Automated infrastructure provisioning

Architecture Alignment

This implementation follows the Architecture Documentation established in Phase 1 and extends the existing Forge CLI with new capabilities.


New CLI Flags

forge new Command Enhancements

forge new <app-name> [options]

New Options:

FlagTypeDefaultDescription
--providerstringsupabaseInfrastructure provider (supabase, neon, aws, gcp)
--supabasestringplatformSupabase project strategy (platform, new, existing)
--supabase-projectstring-Existing Supabase project ID (when using --supabase existing)
--table-strategystringprefixedDatabase table organization (prefixed, app_id)
--scopestringownedApp ownership scope (owned, customer)
--domainstring-Custom domain for the app
--subdomainstring-Subdomain (used with domain)
--skip-dnsbooleanfalseSkip DNS configuration wizard
--auto-deploybooleanfalseAutomatically deploy after creation

Examples

1. Create app with default Supabase (platform project):

forge new my-blog --web nextjs --scope owned

2. Create app with custom domain:

forge new my-blog --domain myblog.com --subdomain blog

3. Create customer app with new Supabase project:

forge new customer-crm --supabase new --scope customer

4. Create app using existing Supabase project:

forge new my-game --supabase existing --supabase-project abc123xyz

5. Create app with app_id table strategy:

forge new shared-blog --table-strategy app_id

Provider Configuration

Supabase Provider

platform (Default for owned apps)

  • Uses the shared platform Supabase project
  • Cost-effective ($0-25/month total)
  • Shared database with RLS isolation
  • Best for apps you own and operate

new (Default for customer apps)

  • Creates a new Supabase project for the app
  • Complete isolation ($0-25/month per project)
  • Best for customer-specific apps
  • Requires Supabase Management API token

existing

  • Uses a specific existing Supabase project
  • Requires project ID
  • Useful for migration or multi-app projects

Configuration Storage

Provider configuration is stored in ops/apps.yaml:

apps:
  my-blog:
    name: "My Personal Blog"
    platforms: ["web"]
    
    # Provider configuration
    provider:
      type: "supabase"
      strategy: "platform"  # platform | new | existing
      project_id: "platform-project-id"  # For platform/existing
      
    # Domain configuration
    domain:
      custom_domain: "myblog.com"
      subdomain: "blog"
      full_domain: "blog.myblog.com"
      
    # Database configuration
    database:
      strategy: "prefixed"  # prefixed | app_id
      table_prefix: "my_blog_"

ops/apps.yaml also centralizes integration credentials under secretTemplates. Each provider lists the secret keys the dashboard, CLI, and job runners expect. The Forge CLI reads that catalog to warn about missing mappings and can surface the required keys when generating .env.generated files or running forge provision.


Table Strategy Implementation

When to use:

  • Apps with different schemas
  • Need for strong isolation
  • Small number of apps (< 20)
  • Easier to export/move

Database structure:

-- Each app gets its own prefixed tables
CREATE TABLE my_blog_posts (...);
CREATE TABLE my_blog_comments (...);

CREATE TABLE my_store_products (...);
CREATE TABLE my_store_orders (...);

Generated code:

// Auto-generated constants
export const TABLES = {
  posts: 'my_blog_posts',
  comments: 'my_blog_comments',
};

// Usage
await supabase.from(TABLES.posts).select('*');

app_id-based Tables

When to use:

  • Apps with similar schemas (all games, all blogs)
  • Large number of apps (20+)
  • Need cross-app analytics

Database structure:

-- Shared tables with app_id column
CREATE TABLE blog_posts (
  app_id UUID REFERENCES apps(id),
  title TEXT,
  content TEXT,
  INDEX idx_app_posts (app_id)
);

Generated code:

// Auto-generated constants
export const APP_ID = 'my-blog-uuid';

// Usage with automatic app scoping
await supabase
  .from('blog_posts')
  .select('*')
  .eq('app_id', APP_ID);

Domain Management Integration

DNS Configuration Wizard

When a domain is specified, the CLI guides users through DNS setup:

$ forge new my-blog --domain myblog.com --subdomain blog

🌐 Domain Configuration
─────────────────────────────────────────

πŸ“‹ DNS Records Required:

Type: CNAME
Name: blog
Value: apps.infiniteapps.ai
TTL: 300

πŸ“ Instructions:
1. Log in to your DNS provider for myblog.com
2. Add a new CNAME record
3. Set the Name/Host to: blog
4. Set the Value/Target to: apps.infiniteapps.ai
5. Set TTL to 300 (5 minutes) or leave as default
6. Save the DNS record

⏳ DNS propagation usually takes 5-30 minutes

Would you like to verify DNS now? (y/n):

DNS Verification

$ forge verify-domain my-blog

πŸ” Verifying DNS for: blog.myblog.com
─────────────────────────────────────────

⏳ Checking DNS records...
βœ… CNAME record found: apps.infiniteapps.ai
βœ… DNS properly configured

πŸ” Issuing SSL certificate...
⏳ This may take 5-10 minutes...
βœ… SSL certificate issued successfully

πŸš€ Domain ready for deployment!

Your app will be available at:
https://blog.myblog.com

Infrastructure Provisioning

Automatic Provisioning Flow

// Simplified flow
async function createApp(options: CreateAppOptions) {
  // 1. Validate configuration
  validateAppConfig(options);
  
  // 2. Provision infrastructure
  if (options.provider === 'supabase') {
    if (options.supabase === 'new') {
      await provisionNewSupabaseProject(options);
    } else if (options.supabase === 'platform') {
      await usePlatformSupabase(options);
    }
  }
  
  // 3. Generate app code
  await generateAppCode(options);
  
  // 4. Create database schema
  if (options.tableStrategy === 'prefixed') {
    await createPrefixedTables(options);
  } else {
    await createAppIdTables(options);
  }
  
  // 5. Configure domain (if provided)
  if (options.domain) {
    await configureDomain(options);
  }
  
  // 6. Deploy (if auto-deploy enabled)
  if (options.autoDeploy) {
    await deployApp(options);
  }
}

Environment Variable Management

Generated .env Files

# apps/my-blog/.env.local (auto-generated)

# Supabase Configuration
NEXT_PUBLIC_SUPABASE_URL=https://platform-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=xxx
SUPABASE_SERVICE_ROLE_KEY=xxx  # Server-side only

# App Configuration
NEXT_PUBLIC_APP_ID=my-blog-uuid
NEXT_PUBLIC_APP_DOMAIN=blog.myblog.com

# Database Configuration
DATABASE_TABLE_PREFIX=my_blog_  # For prefixed strategy
# OR
DATABASE_APP_ID=my-blog-uuid   # For app_id strategy

Doppler Integration

For production, values are synced to Doppler:

# ops/pulumi-secrets-mapping.yaml
supabase:
  platform:
    - supabaseOutput: "url"
      secretName: "SUPABASE__URL"
      
    - supabaseOutput: "anon_key"
      secretName: "SUPABASE__ANON_KEY"

apps:
  my-blog:
    - appOutput: "app_id"
      secretName: "MY_BLOG__APP_ID"
      
    - appOutput: "domain"
      secretName: "MY_BLOG__DOMAIN"

Database Schema Generation

Prefixed Tables Schema

-- Auto-generated: apps/my-blog/prisma/migrations/001_create_tables.sql

-- Create app-specific tables with prefix
CREATE TABLE my_blog_posts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES app_users(id),
  title TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  content TEXT,
  published BOOLEAN DEFAULT FALSE,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE my_blog_comments (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  post_id UUID REFERENCES my_blog_posts(id) ON DELETE CASCADE,
  user_id UUID REFERENCES app_users(id),
  content TEXT NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

-- Indexes for performance
CREATE INDEX idx_my_blog_posts_slug ON my_blog_posts(slug);
CREATE INDEX idx_my_blog_posts_user ON my_blog_posts(user_id);
CREATE INDEX idx_my_blog_comments_post ON my_blog_comments(post_id);

-- RLS Policies
ALTER TABLE my_blog_posts ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can view published posts"
ON my_blog_posts FOR SELECT
USING (published = true OR user_id = auth.uid());

CREATE POLICY "Users can manage own posts"
ON my_blog_posts FOR ALL
USING (user_id = auth.uid());

Code Generation Templates

Generated App Structure

my-blog/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ supabase/
β”‚   β”‚   β”‚   β”œβ”€β”€ client.ts        # Supabase client
β”‚   β”‚   β”‚   └── server.ts        # Server-side client
β”‚   β”‚   β”œβ”€β”€ database/
β”‚   β”‚   β”‚   β”œβ”€β”€ constants.ts     # Table names/App ID
β”‚   β”‚   β”‚   β”œβ”€β”€ queries.ts       # Database queries
β”‚   β”‚   β”‚   └── types.ts         # TypeScript types
β”‚   β”‚   └── domain.ts            # Domain configuration
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ layout.tsx           # Root layout
β”‚   β”‚   └── page.tsx             # Home page
β”‚   └── middleware.ts            # Auth middleware
β”œβ”€β”€ prisma/
β”‚   β”œβ”€β”€ schema.prisma
β”‚   └── migrations/
β”‚       └── 001_create_tables.sql
β”œβ”€β”€ .env.example
β”œβ”€β”€ .env.local                   # Auto-generated
└── package.json

Generated Constants File

// src/lib/database/constants.ts (Prefixed strategy)
export const APP_CONFIG = {
  id: 'my-blog-uuid',
  name: 'My Blog',
  domain: 'blog.myblog.com',
} as const;

export const TABLES = {
  posts: 'my_blog_posts',
  comments: 'my_blog_comments',
} as const;

export type TableName = (typeof TABLES)[keyof typeof TABLES];
// src/lib/database/constants.ts (app_id strategy)
export const APP_CONFIG = {
  id: 'my-blog-uuid',
  name: 'My Blog',
  domain: 'blog.myblog.com',
} as const;

export const TABLES = {
  posts: 'blog_posts',
  comments: 'blog_comments',
} as const;

// All queries automatically include app_id
export type TableName = (typeof TABLES)[keyof typeof TABLES];

CLI Command Reference

forge new

Create a new application.

forge new <app-name> [options]

Options:
  --web <type>              Web framework (nextjs, react)
  --mobile                  Include mobile app
  --flutter                 Use Flutter for mobile
  --provider <provider>     Infrastructure provider (supabase, neon, aws, gcp)
  --supabase <strategy>     Supabase strategy (platform, new, existing)
  --supabase-project <id>   Supabase project ID
  --table-strategy <type>   Table organization (prefixed, app_id)
  --scope <scope>           App scope (owned, customer)
  --domain <domain>         Custom domain
  --subdomain <subdomain>   Subdomain
  --skip-dns                Skip DNS configuration
  --auto-deploy             Auto-deploy after creation
  --config <path>           Config file path
  --output <path>           Output directory
  --overwrite               Overwrite existing files

forge verify-domain

Verify DNS configuration for a domain.

forge verify-domain <app-name>

Checks DNS records and triggers SSL certificate issuance.

forge provision

Provision infrastructure for an application.

forge provision <app-name> <stack> [options]

Arguments:
  app-name                  Application name
  stack                     Deployment stack (local, staging, prod)

Options:
  --provider <provider>     Override provider
  --update-secrets          Update secret manager
  --update-env              Update environment files
  --dry-run                 Simulate without provisioning

forge integrate

Add provider integration to existing app.

forge integrate --provider <provider> --app <app-name>

Retrofits an existing app with provider configuration.

Testing

Unit Tests

// tests/forge-cli/providers.test.ts
describe('Provider Selection', () => {
  it('should default to platform Supabase for owned scope', () => {
    const config = generateProviderConfig({ scope: 'owned' });
    expect(config.provider).toBe('supabase');
    expect(config.strategy).toBe('platform');
  });

  it('should create new Supabase for customer scope', () => {
    const config = generateProviderConfig({ scope: 'customer' });
    expect(config.strategy).toBe('new');
  });
});

Integration Tests

# Create test app
forge new test-app --web nextjs --scope owned --table-strategy prefixed

# Verify generated files
ls apps/test-app/src/lib/database/constants.ts
ls apps/test-app/prisma/migrations/

# Verify configuration
cat ops/apps.yaml | grep test-app

# Clean up
rm -rf apps/test-app

Migration Guide

Migrating Existing Apps

To add provider support to existing apps:

forge integrate --provider supabase --app my-existing-app

This will:
1. Add Supabase client files
2. Update environment configuration
3. Generate database constants
4. Update apps.yaml

Troubleshooting

Common Issues

1. DNS not propagating

# Check DNS manually
dig blog.myblog.com CNAME

# Wait longer and retry
forge verify-domain my-blog --wait

2. Supabase project limit reached

# Use platform strategy instead
forge new my-app --supabase platform

3. Table prefix conflicts

# Use app_id strategy
forge new my-app --table-strategy app_id

Next Steps

After implementing Phase 4, proceed to:

  • Phase 7: Complete integration testing
  • Phase 7: End-to-end deployment pipeline
  • Phase 7: Production readiness checks
  • Phase 7: Documentation finalization

References