Provider Integration
1.0.0How the CLI orchestrates configuration providers and secrets.
On this page
- Forge CLI Provider Integration (Phase 4)
- Overview
- Architecture Alignment
- New CLI Flags
- forge new Command Enhancements
- Examples
- Provider Configuration
- Supabase Provider
- Configuration Storage
- Table Strategy Implementation
- Prefixed Tables (Recommended)
- app_id-based Tables
- Domain Management Integration
- DNS Configuration Wizard
- DNS Verification
- Infrastructure Provisioning
- Automatic Provisioning Flow
- Environment Variable Management
- Generated .env Files
- Doppler Integration
- Database Schema Generation
- Prefixed Tables Schema
- Code Generation Templates
- Generated App Structure
- Generated Constants File
- CLI Command Reference
- forge new
- forge verify-domain
- forge provision
- forge integrate
- Testing
- Unit Tests
- Integration Tests
- Migration Guide
- Migrating Existing Apps
- Troubleshooting
- Common Issues
- Next Steps
- References
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:
| Flag | Type | Default | Description |
|---|---|---|---|
--provider | string | supabase | Infrastructure provider (supabase, neon, aws, gcp) |
--supabase | string | platform | Supabase project strategy (platform, new, existing) |
--supabase-project | string | - | Existing Supabase project ID (when using --supabase existing) |
--table-strategy | string | prefixed | Database table organization (prefixed, app_id) |
--scope | string | owned | App ownership scope (owned, customer) |
--domain | string | - | Custom domain for the app |
--subdomain | string | - | Subdomain (used with domain) |
--skip-dns | boolean | false | Skip DNS configuration wizard |
--auto-deploy | boolean | false | Automatically 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
Prefixed Tables (Recommended)
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