Skip to content

validate-implementation

Access: /template validate-implementation or /t validate-implementation

Validates your MakerKit implementation against best practices, ensuring proper setup and configuration.

What It Does

The validate-implementation command helps you:

  • Verify correct MakerKit patterns usage
  • Check database schema consistency
  • Validate authentication setup
  • Ensure proper TypeScript types
  • Verify environment configuration
  • Check for common implementation mistakes
  • Generate validation report

Usage

bash
/template validate-implementation "Description"
# or
/t validate-implementation "Description"

When prompted, specify:

  • Validation depth (quick or comprehensive)
  • Areas to focus on
  • Strict mode (fail on warnings)

Prerequisites

  • A MakerKit project implementation
  • All dependencies installed
  • Commercial MakerKit license from MakerKit

What Gets Validated

1. Project Structure

typescript
// Validate MakerKit project structure
async function validateProjectStructure() {
  const requiredPaths = [
    // Monorepo structure
    'apps/web',
    'packages/ui',
    'packages/shared',
    'packages/billing',
    'packages/auth',
    'packages/supabase',
    
    // Configuration files
    'apps/web/next.config.js',
    'apps/web/tailwind.config.js',
    'turbo.json',
    'pnpm-workspace.yaml',
    
    // Environment files
    '.env.example',
    '.env.local',
  ];
  
  const issues = [];
  
  for (const path of requiredPaths) {
    if (!await pathExists(path)) {
      issues.push({
        severity: path.includes('.env.example') ? 'high' : 'medium',
        path,
        message: `Required path missing: ${path}`,
        fix: `Ensure MakerKit is properly initialized`,
      });
    }
  }
  
  // Check for incorrect structures
  const antiPatterns = [
    { path: 'src', message: 'Using src directory instead of app directory' },
    { path: 'pages', message: 'Using pages directory instead of app router' },
    { path: '.env', message: '.env file should not be committed' },
  ];
  
  for (const { path, message } of antiPatterns) {
    if (await pathExists(path)) {
      issues.push({
        severity: 'medium',
        path,
        message,
        fix: 'Follow MakerKit conventions',
      });
    }
  }
  
  return issues;
}

2. Database Schema

typescript
// Validate database schema
async function validateDatabaseSchema() {
  const client = getSupabaseServerClient({ admin: true });
  const issues = [];
  
  // Required tables
  const requiredTables = [
    'users',
    'accounts',
    'accounts_account_members',
    'subscriptions',
    'invitations',
  ];
  
  const { data: tables } = await client
    .from('information_schema.tables')
    .select('table_name')
    .eq('table_schema', 'public');
  
  const existingTables = tables?.map(t => t.table_name) || [];
  
  for (const table of requiredTables) {
    if (!existingTables.includes(table)) {
      issues.push({
        severity: 'critical',
        table,
        message: `Required table missing: ${table}`,
        fix: 'Run database migrations',
      });
    }
  }
  
  // Validate RLS
  for (const table of existingTables) {
    const { data: rlsEnabled } = await client
      .from('pg_tables')
      .select('rowsecurity')
      .eq('schemaname', 'public')
      .eq('tablename', table)
      .single();
    
    if (!rlsEnabled?.rowsecurity) {
      issues.push({
        severity: 'critical',
        table,
        message: `RLS not enabled on table: ${table}`,
        fix: `ALTER TABLE public.${table} ENABLE ROW LEVEL SECURITY;`,
      });
    }
  }
  
  // Check for proper indexes
  const indexChecks = [
    { table: 'users', column: 'email' },
    { table: 'accounts', column: 'slug' },
    { table: 'accounts_account_members', columns: ['account_id', 'user_id'] },
  ];
  
  for (const check of indexChecks) {
    const hasIndex = await checkIndexExists(check);
    if (!hasIndex) {
      issues.push({
        severity: 'medium',
        table: check.table,
        message: `Missing index on ${check.column || check.columns.join(', ')}`,
        fix: 'Create index for better performance',
      });
    }
  }
  
  return issues;
}

3. Authentication Setup

typescript
// Validate auth configuration
async function validateAuthentication() {
  const issues = [];
  
  // Check Supabase configuration
  if (!process.env.NEXT_PUBLIC_SUPABASE_URL) {
    issues.push({
      severity: 'critical',
      config: 'NEXT_PUBLIC_SUPABASE_URL',
      message: 'Supabase URL not configured',
    });
  }
  
  if (!process.env.SUPABASE_SERVICE_ROLE_KEY) {
    issues.push({
      severity: 'critical',
      config: 'SUPABASE_SERVICE_ROLE_KEY',
      message: 'Service role key not configured',
    });
  }
  
  // Check auth providers
  const { data: providers } = await supabase
    .from('auth.providers')
    .select('*')
    .eq('enabled', true);
  
  if (!providers || providers.length === 0) {
    issues.push({
      severity: 'high',
      message: 'No auth providers enabled',
      fix: 'Enable at least email authentication',
    });
  }
  
  // Check auth hooks implementation
  const authFiles = [
    'packages/auth/src/hooks/use-user.ts',
    'packages/auth/src/hooks/use-sign-out.ts',
    'packages/auth/src/components/auth-provider.tsx',
  ];
  
  for (const file of authFiles) {
    if (!await pathExists(file)) {
      issues.push({
        severity: 'high',
        file,
        message: 'Required auth file missing',
      });
    }
  }
  
  // Check middleware
  const middlewarePath = 'apps/web/middleware.ts';
  if (await pathExists(middlewarePath)) {
    const content = await fs.readFile(middlewarePath, 'utf-8');
    
    if (!content.includes('createMiddlewareClient')) {
      issues.push({
        severity: 'high',
        file: middlewarePath,
        message: 'Middleware not using Supabase auth',
        fix: 'Implement proper auth middleware',
      });
    }
  } else {
    issues.push({
      severity: 'high',
      message: 'Auth middleware missing',
      fix: 'Create middleware.ts for route protection',
    });
  }
  
  return issues;
}

4. TypeScript Configuration

typescript
// Validate TypeScript setup
async function validateTypeScript() {
  const issues = [];
  
  // Check tsconfig
  const tsconfigPath = 'tsconfig.json';
  if (!await pathExists(tsconfigPath)) {
    issues.push({
      severity: 'critical',
      message: 'tsconfig.json missing',
    });
    return issues;
  }
  
  const tsconfig = JSON.parse(await fs.readFile(tsconfigPath, 'utf-8'));
  
  // Required compiler options
  const requiredOptions = {
    strict: true,
    skipLibCheck: true,
    esModuleInterop: true,
    moduleResolution: 'node',
  };
  
  for (const [option, value] of Object.entries(requiredOptions)) {
    if (tsconfig.compilerOptions?.[option] !== value) {
      issues.push({
        severity: 'medium',
        option,
        message: `TypeScript option ${option} should be ${value}`,
      });
    }
  }
  
  // Check for type errors
  try {
    const { stdout, stderr } = await execAsync('pnpm typecheck');
    if (stderr) {
      const errors = stderr.split('\n').filter(line => line.includes('error'));
      issues.push({
        severity: 'high',
        message: `TypeScript errors found: ${errors.length}`,
        errors: errors.slice(0, 10),
        fix: 'Fix type errors before deployment',
      });
    }
  } catch (error) {
    issues.push({
      severity: 'high',
      message: 'TypeScript compilation failed',
      error: error.message,
    });
  }
  
  // Check for generated types
  const typesPath = 'packages/supabase/src/types/database.types.ts';
  if (!await pathExists(typesPath)) {
    issues.push({
      severity: 'high',
      message: 'Database types not generated',
      fix: 'Run: pnpm db:typegen',
    });
  } else {
    // Check if types are up to date
    const typesMtime = (await fs.stat(typesPath)).mtime;
    const migrationFiles = await glob('apps/web/supabase/migrations/*.sql');
    
    for (const migration of migrationFiles) {
      const migrationMtime = (await fs.stat(migration)).mtime;
      if (migrationMtime > typesMtime) {
        issues.push({
          severity: 'medium',
          message: 'Database types may be outdated',
          fix: 'Regenerate types: pnpm db:typegen',
        });
        break;
      }
    }
  }
  
  return issues;
}

5. MakerKit Patterns

typescript
// Validate MakerKit pattern usage
async function validateMakerKitPatterns() {
  const issues = [];
  
  // Check Server Actions usage
  const serverActionFiles = await glob('apps/web/**/*actions*.ts');
  
  for (const file of serverActionFiles) {
    const content = await fs.readFile(file, 'utf-8');
    
    // Check for enhanceAction usage
    if (!content.includes('enhanceAction')) {
      issues.push({
        severity: 'medium',
        file,
        pattern: 'Server Actions',
        message: 'Not using enhanceAction wrapper',
        fix: 'Wrap server actions with enhanceAction for auth and validation',
      });
    }
    
    // Check for proper error handling
    if (!content.includes('try') || !content.includes('catch')) {
      issues.push({
        severity: 'low',
        file,
        pattern: 'Error Handling',
        message: 'Missing error handling in server actions',
      });
    }
  }
  
  // Check data fetching patterns
  const pageFiles = await glob('apps/web/app/**/page.tsx');
  
  for (const file of pageFiles) {
    const content = await fs.readFile(file, 'utf-8');
    
    // Check for client-side data fetching in server components
    if (content.includes('useEffect') && content.includes('fetch')) {
      issues.push({
        severity: 'high',
        file,
        pattern: 'Data Fetching',
        message: 'Using client-side fetch in server component',
        fix: 'Fetch data at the server component level',
      });
    }
  }
  
  // Check UI component usage
  const componentFiles = await glob('apps/web/**/*.tsx');
  
  for (const file of componentFiles) {
    const content = await fs.readFile(file, 'utf-8');
    
    // Check for custom implementations of MakerKit components
    if (content.includes('className="button"') && !content.includes('@kit/ui')) {
      issues.push({
        severity: 'low',
        file,
        pattern: 'UI Components',
        message: 'Not using MakerKit UI components',
        fix: 'Use components from @kit/ui package',
      });
    }
  }
  
  return issues;
}

6. Environment Configuration

typescript
// Validate environment setup
async function validateEnvironment() {
  const issues = [];
  
  // Required environment variables
  const requiredVars = [
    // Supabase
    'NEXT_PUBLIC_SUPABASE_URL',
    'NEXT_PUBLIC_SUPABASE_ANON_KEY',
    'SUPABASE_SERVICE_ROLE_KEY',
    
    // App
    'NEXT_PUBLIC_APP_URL',
    'NEXT_PUBLIC_APP_NAME',
    
    // Optional but recommended
    'STRIPE_SECRET_KEY',
    'STRIPE_WEBHOOK_SECRET',
    'RESEND_API_KEY',
  ];
  
  const envExample = await fs.readFile('.env.example', 'utf-8').catch(() => '');
  
  for (const varName of requiredVars) {
    // Check if documented
    if (!envExample.includes(varName)) {
      issues.push({
        severity: 'medium',
        variable: varName,
        message: 'Not documented in .env.example',
      });
    }
    
    // Check if set (in development)
    if (!process.env[varName] && requiredVars.slice(0, 5).includes(varName)) {
      issues.push({
        severity: 'high',
        variable: varName,
        message: 'Required environment variable not set',
      });
    }
  }
  
  // Check for exposed secrets
  const gitFiles = await execAsync('git ls-files').then(r => r.stdout.split('\n'));
  
  for (const file of gitFiles) {
    if (file.includes('.env') && !file.includes('.example')) {
      issues.push({
        severity: 'critical',
        file,
        message: 'Environment file tracked in git',
        fix: 'Remove from git and add to .gitignore',
      });
    }
  }
  
  return issues;
}

7. Billing Integration

typescript
// Validate billing setup
async function validateBilling() {
  const issues = [];
  
  // Check Stripe configuration
  if (process.env.STRIPE_SECRET_KEY) {
    // Verify webhook endpoint
    const webhookPath = 'apps/web/app/api/stripe/webhook/route.ts';
    if (!await pathExists(webhookPath)) {
      issues.push({
        severity: 'high',
        message: 'Stripe webhook endpoint missing',
        fix: 'Create webhook handler',
      });
    } else {
      const content = await fs.readFile(webhookPath, 'utf-8');
      
      if (!content.includes('stripe.webhooks.constructEvent')) {
        issues.push({
          severity: 'high',
          message: 'Webhook not verifying signatures',
          fix: 'Use stripe.webhooks.constructEvent for security',
        });
      }
    }
    
    // Check subscription sync
    const hasSubscriptionTable = await checkTableExists('subscriptions');
    if (!hasSubscriptionTable) {
      issues.push({
        severity: 'high',
        message: 'Subscriptions table missing',
        fix: 'Run billing migrations',
      });
    }
  }
  
  return issues;
}

8. Validation Report

typescript
// Generate validation report
export async function generateValidationReport() {
  console.log('✅ Starting MakerKit Validation...\n');
  
  const report = {
    timestamp: new Date().toISOString(),
    valid: true,
    summary: {
      passed: 0,
      warnings: 0,
      errors: 0,
    },
    categories: {},
  };
  
  // Run all validations
  const validations = [
    { name: 'Project Structure', fn: validateProjectStructure },
    { name: 'Database Schema', fn: validateDatabaseSchema },
    { name: 'Authentication', fn: validateAuthentication },
    { name: 'TypeScript', fn: validateTypeScript },
    { name: 'MakerKit Patterns', fn: validateMakerKitPatterns },
    { name: 'Environment', fn: validateEnvironment },
    { name: 'Billing', fn: validateBilling },
  ];
  
  for (const validation of validations) {
    console.log(`Validating ${validation.name}...`);
    
    try {
      const issues = await validation.fn();
      report.categories[validation.name] = issues;
      
      // Update counts
      for (const issue of issues) {
        if (issue.severity === 'critical' || issue.severity === 'high') {
          report.summary.errors++;
          report.valid = false;
        } else {
          report.summary.warnings++;
        }
      }
      
      if (issues.length === 0) {
        report.summary.passed++;
      }
    } catch (error) {
      report.categories[validation.name] = [{
        severity: 'critical',
        message: `Validation failed: ${error.message}`,
      }];
      report.summary.errors++;
      report.valid = false;
    }
  }
  
  // Generate reports
  const markdown = generateValidationMarkdown(report);
  await fs.writeFile('validation-report.md', markdown);
  
  const json = JSON.stringify(report, null, 2);
  await fs.writeFile('validation-report.json', json);
  
  // Display summary
  console.log('\n📊 Validation Complete!');
  console.log(`Status: ${report.valid ? '✅ VALID' : '❌ INVALID'}`);
  console.log(`Passed: ${report.summary.passed}/${validations.length}`);
  console.log(`Errors: ${report.summary.errors}`);
  console.log(`Warnings: ${report.summary.warnings}`);
  console.log('\n📄 Report saved to validation-report.md');
  
  return report;
}

function generateValidationMarkdown(report) {
  let markdown = '# MakerKit Implementation Validation Report\n\n';
  markdown += `Generated: ${report.timestamp}\n\n`;
  markdown += `## Summary\n\n`;
  markdown += `- **Status**: ${report.valid ? '✅ VALID' : '❌ INVALID'}\n`;
  markdown += `- **Errors**: ${report.summary.errors}\n`;
  markdown += `- **Warnings**: ${report.summary.warnings}\n\n`;
  
  for (const [category, issues] of Object.entries(report.categories)) {
    markdown += `## ${category}\n\n`;
    
    if (issues.length === 0) {
      markdown += '✅ All checks passed\n\n';
    } else {
      for (const issue of issues) {
        const icon = issue.severity === 'critical' || issue.severity === 'high' ? '❌' : '⚠️';
        markdown += `${icon} **${issue.severity.toUpperCase()}**: ${issue.message}\n`;
        
        if (issue.fix) {
          markdown += `   - Fix: ${issue.fix}\n`;
        }
        
        markdown += '\n';
      }
    }
  }
  
  return markdown;
}

Auto-Fix Capabilities

Some issues can be fixed automatically:

typescript
export async function autoFixValidationIssues() {
  console.log('🔧 Attempting automatic fixes...\n');
  
  // Generate missing types
  await execAsync('pnpm db:typegen');
  
  // Create missing directories
  await createMissingDirectories();
  
  // Update TypeScript config
  await fixTypeScriptConfig();
  
  // Generate .env.example
  await generateEnvExample();
  
  console.log('✅ Automatic fixes applied');
}

License Requirement

Important: This command requires a commercial MakerKit license from https://makerkit.dev?atp=MqaGgc MakerKit is a premium SaaS starter kit and requires proper licensing for commercial use.

Built with ❤️ for the AI Coding community, by Praney Behl