From e61711431026bd70a7be11840af5f6ad1aa00df7 Mon Sep 17 00:00:00 2001 From: Oussama Douhou Date: Sat, 10 Jan 2026 12:32:54 +0100 Subject: [PATCH] refactor: enterprise-grade project structure - Move test files to tests/ - Archive session notes to docs/archive/ - Remove temp/diagnostic files - Clean src/ to only contain production code --- docs/{ => archive}/CLAUDE_CODE_MCP_SETUP.md | 0 docs/{ => archive}/DEPLOYMENT_NOTES.md | 0 docs/{ => archive}/DEPLOYMENT_PROOF.md | 0 docs/{ => archive}/HTTP_SERVER_UPDATE.md | 0 docs/{ => archive}/LOGIC_VALIDATION.md | 0 docs/{ => archive}/REALTIME_PROGRESS_FIX.md | 0 src/diagnose-app-create.ts | 49 --- src/index-legacy.ts.backup | 351 ----------------- src/index-production.ts | 374 ------------------- {src => tests}/test-api-formats.ts | 0 {src => tests}/test-clients.ts | 0 {src => tests}/test-deploy-persistent.ts | 0 {src => tests}/test-deployment-proof.ts | 0 {src => tests}/test-production-deployment.ts | 0 {src => tests}/validation.test.ts | 0 15 files changed, 774 deletions(-) rename docs/{ => archive}/CLAUDE_CODE_MCP_SETUP.md (100%) rename docs/{ => archive}/DEPLOYMENT_NOTES.md (100%) rename docs/{ => archive}/DEPLOYMENT_PROOF.md (100%) rename docs/{ => archive}/HTTP_SERVER_UPDATE.md (100%) rename docs/{ => archive}/LOGIC_VALIDATION.md (100%) rename docs/{ => archive}/REALTIME_PROGRESS_FIX.md (100%) delete mode 100644 src/diagnose-app-create.ts delete mode 100644 src/index-legacy.ts.backup delete mode 100644 src/index-production.ts rename {src => tests}/test-api-formats.ts (100%) rename {src => tests}/test-clients.ts (100%) rename {src => tests}/test-deploy-persistent.ts (100%) rename {src => tests}/test-deployment-proof.ts (100%) rename {src => tests}/test-production-deployment.ts (100%) rename {src => tests}/validation.test.ts (100%) diff --git a/docs/CLAUDE_CODE_MCP_SETUP.md b/docs/archive/CLAUDE_CODE_MCP_SETUP.md similarity index 100% rename from docs/CLAUDE_CODE_MCP_SETUP.md rename to docs/archive/CLAUDE_CODE_MCP_SETUP.md diff --git a/docs/DEPLOYMENT_NOTES.md b/docs/archive/DEPLOYMENT_NOTES.md similarity index 100% rename from docs/DEPLOYMENT_NOTES.md rename to docs/archive/DEPLOYMENT_NOTES.md diff --git a/docs/DEPLOYMENT_PROOF.md b/docs/archive/DEPLOYMENT_PROOF.md similarity index 100% rename from docs/DEPLOYMENT_PROOF.md rename to docs/archive/DEPLOYMENT_PROOF.md diff --git a/docs/HTTP_SERVER_UPDATE.md b/docs/archive/HTTP_SERVER_UPDATE.md similarity index 100% rename from docs/HTTP_SERVER_UPDATE.md rename to docs/archive/HTTP_SERVER_UPDATE.md diff --git a/docs/LOGIC_VALIDATION.md b/docs/archive/LOGIC_VALIDATION.md similarity index 100% rename from docs/LOGIC_VALIDATION.md rename to docs/archive/LOGIC_VALIDATION.md diff --git a/docs/REALTIME_PROGRESS_FIX.md b/docs/archive/REALTIME_PROGRESS_FIX.md similarity index 100% rename from docs/REALTIME_PROGRESS_FIX.md rename to docs/archive/REALTIME_PROGRESS_FIX.md diff --git a/src/diagnose-app-create.ts b/src/diagnose-app-create.ts deleted file mode 100644 index 3722504..0000000 --- a/src/diagnose-app-create.ts +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bun -/** - * Diagnostic script to test application.create API call - * Captures exact error message and request/response - */ - -import { createDokployClient } from './api/dokploy.js'; - -console.log('═══════════════════════════════════════'); -console.log(' Diagnosing application.create API'); -console.log('═══════════════════════════════════════\n'); - -try { - const client = createDokployClient(); - - // Use existing project ID from earlier test - const projectId = 'MV2b-c1hIW4-Dww8Xoinj'; - const appName = `test-diagnostic-${Date.now()}`; - - console.log(`Project ID: ${projectId}`); - console.log(`App Name: ${appName}`); - console.log(`Docker Image: nginx:alpine`); - console.log(); - - console.log('Making API call...\n'); - - const application = await client.createApplication( - appName, - projectId, - 'nginx:alpine' - ); - - console.log('✅ Success! Application created:'); - console.log(JSON.stringify(application, null, 2)); - -} catch (error) { - console.error('❌ Failed to create application\n'); - console.error('Error details:'); - - if (error instanceof Error) { - console.error(`Message: ${error.message}`); - console.error(`\nStack trace:`); - console.error(error.stack); - } else { - console.error(error); - } - - process.exit(1); -} diff --git a/src/index-legacy.ts.backup b/src/index-legacy.ts.backup deleted file mode 100644 index 6ec99f8..0000000 --- a/src/index-legacy.ts.backup +++ /dev/null @@ -1,351 +0,0 @@ -import { Hono } from 'hono'; -import { cors } from 'hono/cors'; -import { logger } from 'hono/logger'; -import { serveStatic } from 'hono/bun'; -import { streamSSE } from 'hono/streaming'; -import { createDokployClient } from './api/dokploy.js'; - -const PORT = parseInt(process.env.PORT || '3000', 10); -const HOST = process.env.HOST || '0.0.0.0'; - -// Deployment state tracking -interface DeploymentState { - id: string; - name: string; - status: 'initializing' | 'creating_project' | 'creating_application' | 'deploying' | 'completed' | 'failed'; - url?: string; - error?: string; - createdAt: Date; - projectId?: string; - applicationId?: string; - progress: number; - currentStep: string; -} - -const deployments = new Map(); - -// Generate a unique deployment ID -function generateDeploymentId(): string { - return `dep_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; -} - -// Validate stack name -function validateStackName(name: string): { valid: boolean; error?: string } { - if (!name || typeof name !== 'string') { - return { valid: false, error: 'Name is required' }; - } - - const trimmedName = name.trim().toLowerCase(); - - if (trimmedName.length < 3 || trimmedName.length > 20) { - return { valid: false, error: 'Name must be between 3 and 20 characters' }; - } - - if (!/^[a-z0-9-]+$/.test(trimmedName)) { - return { valid: false, error: 'Name can only contain lowercase letters, numbers, and hyphens' }; - } - - if (trimmedName.startsWith('-') || trimmedName.endsWith('-')) { - return { valid: false, error: 'Name cannot start or end with a hyphen' }; - } - - const reservedNames = (process.env.RESERVED_NAMES || 'admin,api,www,root,system,test,demo,portal').split(','); - if (reservedNames.includes(trimmedName)) { - return { valid: false, error: `Name "${trimmedName}" is reserved` }; - } - - return { valid: true }; -} - -// Main deployment orchestration -async function deployStack(deploymentId: string): Promise { - const deployment = deployments.get(deploymentId); - if (!deployment) { - throw new Error('Deployment not found'); - } - - try { - const dokployClient = createDokployClient(); - const domain = `${deployment.name}.${process.env.STACK_DOMAIN_SUFFIX || 'ai.flexinit.nl'}`; - - // Step 1: Create Dokploy project - deployment.status = 'creating_project'; - deployment.progress = 25; - deployment.currentStep = 'Creating Dokploy project'; - deployments.set(deploymentId, { ...deployment }); - - const projectName = `ai-stack-${deployment.name}`; - let project = await dokployClient.findProjectByName(projectName); - - if (!project) { - project = await dokployClient.createProject( - projectName, - `AI Stack for ${deployment.name}` - ); - } - - deployment.projectId = project.projectId; - deployments.set(deploymentId, { ...deployment }); - - // Step 2: Create application - deployment.status = 'creating_application'; - deployment.progress = 50; - deployment.currentStep = 'Creating application container'; - deployments.set(deploymentId, { ...deployment }); - - const dockerImage = process.env.STACK_IMAGE || 'git.app.flexinit.nl/oussamadouhou/oh-my-opencode-free:latest'; - const application = await dokployClient.createApplication( - `opencode-${deployment.name}`, - project.projectId, - dockerImage - ); - - deployment.applicationId = application.applicationId; - deployments.set(deploymentId, { ...deployment }); - - // Step 3: Configure domain - deployment.progress = 70; - deployment.currentStep = 'Configuring domain'; - deployments.set(deploymentId, { ...deployment }); - - await dokployClient.createDomain( - domain, - application.applicationId, - true, - 8080 - ); - - // Step 4: Deploy application - deployment.status = 'deploying'; - deployment.progress = 85; - deployment.currentStep = 'Deploying application'; - deployments.set(deploymentId, { ...deployment }); - - await dokployClient.deployApplication(application.applicationId); - - // Mark as completed - deployment.status = 'completed'; - deployment.progress = 100; - deployment.currentStep = 'Deployment complete'; - deployment.url = `https://${domain}`; - deployments.set(deploymentId, { ...deployment }); - - } catch (error) { - deployment.status = 'failed'; - deployment.error = error instanceof Error ? error.message : 'Unknown error'; - deployment.currentStep = 'Deployment failed'; - deployments.set(deploymentId, { ...deployment }); - throw error; - } -} - -const app = new Hono(); - -app.use('*', logger()); -app.use('*', cors()); - -app.get('/health', (c) => { - return c.json({ - status: 'healthy', - timestamp: new Date().toISOString(), - version: '0.1.0', - service: 'ai-stack-deployer', - activeDeployments: deployments.size - }); -}); - -// Root path now served by static frontend (removed JSON response) -// app.get('/', ...) - see bottom of file for static file serving - -// Deploy endpoint -app.post('/api/deploy', async (c) => { - try { - const body = await c.req.json(); - const { name } = body; - - // Validate name - const validation = validateStackName(name); - if (!validation.valid) { - return c.json({ - success: false, - error: validation.error, - code: 'INVALID_NAME' - }, 400); - } - - const normalizedName = name.trim().toLowerCase(); - - // Check if name is already taken - const dokployClient = createDokployClient(); - const projectName = `ai-stack-${normalizedName}`; - const existingProject = await dokployClient.findProjectByName(projectName); - - if (existingProject) { - return c.json({ - success: false, - error: 'Name already taken', - code: 'NAME_EXISTS' - }, 409); - } - - // Create deployment - const deploymentId = generateDeploymentId(); - const deployment: DeploymentState = { - id: deploymentId, - name: normalizedName, - status: 'initializing', - createdAt: new Date(), - progress: 0, - currentStep: 'Initializing deployment' - }; - - deployments.set(deploymentId, deployment); - - // Start deployment in background - deployStack(deploymentId).catch(err => { - console.error(`Deployment ${deploymentId} failed:`, err); - }); - - return c.json({ - success: true, - deploymentId, - url: `https://${normalizedName}.${process.env.STACK_DOMAIN_SUFFIX || 'ai.flexinit.nl'}`, - statusEndpoint: `/api/status/${deploymentId}` - }); - - } catch (error) { - console.error('Deploy endpoint error:', error); - return c.json({ - success: false, - error: error instanceof Error ? error.message : 'Internal server error', - code: 'INTERNAL_ERROR' - }, 500); - } -}); - -// Status endpoint with SSE -app.get('/api/status/:deploymentId', (c) => { - const deploymentId = c.req.param('deploymentId'); - const deployment = deployments.get(deploymentId); - - if (!deployment) { - return c.json({ - success: false, - error: 'Deployment not found', - code: 'NOT_FOUND' - }, 404); - } - - return streamSSE(c, async (stream) => { - let lastStatus = ''; - - try { - // Stream updates until deployment completes or fails - while (true) { - const currentDeployment = deployments.get(deploymentId); - - if (!currentDeployment) { - await stream.writeSSE({ - event: 'error', - data: JSON.stringify({ message: 'Deployment not found' }) - }); - break; - } - - // Send update if status changed - const currentStatus = JSON.stringify(currentDeployment); - if (currentStatus !== lastStatus) { - await stream.writeSSE({ - event: 'progress', - data: JSON.stringify({ - status: currentDeployment.status, - progress: currentDeployment.progress, - currentStep: currentDeployment.currentStep, - url: currentDeployment.url, - error: currentDeployment.error - }) - }); - lastStatus = currentStatus; - } - - // Exit if terminal state - if (currentDeployment.status === 'completed') { - await stream.writeSSE({ - event: 'complete', - data: JSON.stringify({ - url: currentDeployment.url, - status: 'ready' - }) - }); - break; - } - - if (currentDeployment.status === 'failed') { - await stream.writeSSE({ - event: 'error', - data: JSON.stringify({ - message: currentDeployment.error || 'Deployment failed', - status: 'failed' - }) - }); - break; - } - - // Wait before next check - await stream.sleep(1000); - } - } catch (error) { - console.error('SSE stream error:', error); - } - }); -}); - -// Check name availability -app.get('/api/check/:name', async (c) => { - try { - const name = c.req.param('name'); - - // Validate name format - const validation = validateStackName(name); - if (!validation.valid) { - return c.json({ - available: false, - valid: false, - error: validation.error - }); - } - - const normalizedName = name.trim().toLowerCase(); - - // Check if project exists - const dokployClient = createDokployClient(); - const projectName = `ai-stack-${normalizedName}`; - const existingProject = await dokployClient.findProjectByName(projectName); - - return c.json({ - available: !existingProject, - valid: true, - name: normalizedName - }); - - } catch (error) { - console.error('Check endpoint error:', error); - return c.json({ - available: false, - valid: false, - error: 'Failed to check availability' - }, 500); - } -}); - -// Serve static files (frontend) -app.use('/static/*', serveStatic({ root: './src/frontend' })); -app.use('/*', serveStatic({ root: './src/frontend', path: '/index.html' })); - -console.log(`🚀 AI Stack Deployer starting on http://${HOST}:${PORT}`); - -export default { - port: PORT, - hostname: HOST, - fetch: app.fetch, -}; diff --git a/src/index-production.ts b/src/index-production.ts deleted file mode 100644 index 143e711..0000000 --- a/src/index-production.ts +++ /dev/null @@ -1,374 +0,0 @@ -import { Hono } from 'hono'; -import { cors } from 'hono/cors'; -import { logger } from 'hono/logger'; -import { serveStatic } from 'hono/bun'; -import { streamSSE } from 'hono/streaming'; -import { createProductionDokployClient } from './api/dokploy-production.js'; -import { ProductionDeployer } from './orchestrator/production-deployer.js'; -import type { DeploymentState as OrchestratorDeploymentState } from './orchestrator/production-deployer.js'; - -const PORT = parseInt(process.env.PORT || '3000', 10); -const HOST = process.env.HOST || '0.0.0.0'; - -// Extended deployment state for HTTP server (adds logs) -interface HttpDeploymentState extends OrchestratorDeploymentState { - logs: string[]; -} - -const deployments = new Map(); - -// Generate a unique deployment ID -function generateDeploymentId(): string { - return `dep_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; -} - -// Validate stack name -function validateStackName(name: string): { valid: boolean; error?: string } { - if (!name || typeof name !== 'string') { - return { valid: false, error: 'Name is required' }; - } - - const trimmedName = name.trim().toLowerCase(); - - if (trimmedName.length < 3 || trimmedName.length > 20) { - return { valid: false, error: 'Name must be between 3 and 20 characters' }; - } - - if (!/^[a-z0-9-]+$/.test(trimmedName)) { - return { valid: false, error: 'Name can only contain lowercase letters, numbers, and hyphens' }; - } - - if (trimmedName.startsWith('-') || trimmedName.endsWith('-')) { - return { valid: false, error: 'Name cannot start or end with a hyphen' }; - } - - const reservedNames = (process.env.RESERVED_NAMES || 'admin,api,www,root,system,test,demo,portal').split(','); - if (reservedNames.includes(trimmedName)) { - return { valid: false, error: `Name "${trimmedName}" is reserved` }; - } - - return { valid: true }; -} - -// Main deployment orchestration using production components -async function deployStack(deploymentId: string): Promise { - const deployment = deployments.get(deploymentId); - if (!deployment) { - throw new Error('Deployment not found'); - } - - try { - const client = createProductionDokployClient(); - const deployer = new ProductionDeployer(client); - - // Execute deployment with production orchestrator - const result = await deployer.deploy({ - stackName: deployment.stackName, - dockerImage: process.env.STACK_IMAGE || 'git.app.flexinit.nl/oussamadouhou/oh-my-opencode-free:latest', - domainSuffix: process.env.STACK_DOMAIN_SUFFIX || 'ai.flexinit.nl', - port: 8080, - healthCheckTimeout: 60000, // 60 seconds - healthCheckInterval: 5000, // 5 seconds - }); - - // Update deployment state with orchestrator result - deployment.phase = result.state.phase; - deployment.status = result.state.status; - deployment.progress = result.state.progress; - deployment.message = result.state.message; - deployment.url = result.state.url; - deployment.error = result.state.error; - deployment.resources = result.state.resources; - deployment.timestamps = result.state.timestamps; - deployment.logs = result.logs; - - deployments.set(deploymentId, { ...deployment }); - - } catch (error) { - // Deployment failed catastrophically (before orchestrator could handle it) - deployment.status = 'failure'; - deployment.phase = 'failed'; - deployment.error = { - phase: deployment.phase, - message: error instanceof Error ? error.message : 'Unknown error', - code: 'DEPLOYMENT_FAILED', - }; - deployment.message = 'Deployment failed'; - deployment.timestamps.completed = new Date().toISOString(); - deployments.set(deploymentId, { ...deployment }); - throw error; - } -} - -const app = new Hono(); - -app.use('*', logger()); -app.use('*', cors()); - -app.get('/health', (c) => { - return c.json({ - status: 'healthy', - timestamp: new Date().toISOString(), - version: '0.2.0', // Bumped version for production components - service: 'ai-stack-deployer', - activeDeployments: deployments.size, - features: { - productionClient: true, - retryLogic: true, - circuitBreaker: true, - autoRollback: true, - healthVerification: true, - } - }); -}); - -// Deploy endpoint -app.post('/api/deploy', async (c) => { - try { - const body = await c.req.json(); - const { name } = body; - - // Validate name - const validation = validateStackName(name); - if (!validation.valid) { - return c.json({ - success: false, - error: validation.error, - code: 'INVALID_NAME' - }, 400); - } - - const normalizedName = name.trim().toLowerCase(); - - // Check if name is already taken - const client = createProductionDokployClient(); - const projectName = `ai-stack-${normalizedName}`; - const existingProject = await client.findProjectByName(projectName); - - if (existingProject) { - return c.json({ - success: false, - error: 'Name already taken', - code: 'NAME_EXISTS' - }, 409); - } - - // Create deployment state - const deploymentId = generateDeploymentId(); - const deployment: HttpDeploymentState = { - id: deploymentId, - stackName: normalizedName, - phase: 'initializing', - status: 'in_progress', - progress: 0, - message: 'Initializing deployment', - resources: {}, - timestamps: { - started: new Date().toISOString(), - }, - logs: [], - }; - - deployments.set(deploymentId, deployment); - - // Start deployment in background - deployStack(deploymentId).catch(err => { - console.error(`Deployment ${deploymentId} failed:`, err); - }); - - return c.json({ - success: true, - deploymentId, - url: `https://${normalizedName}.${process.env.STACK_DOMAIN_SUFFIX || 'ai.flexinit.nl'}`, - statusEndpoint: `/api/status/${deploymentId}` - }); - - } catch (error) { - console.error('Deploy endpoint error:', error); - return c.json({ - success: false, - error: error instanceof Error ? error.message : 'Internal server error', - code: 'INTERNAL_ERROR' - }, 500); - } -}); - -// Status endpoint with SSE -app.get('/api/status/:deploymentId', (c) => { - const deploymentId = c.req.param('deploymentId'); - const deployment = deployments.get(deploymentId); - - if (!deployment) { - return c.json({ - success: false, - error: 'Deployment not found', - code: 'NOT_FOUND' - }, 404); - } - - return streamSSE(c, async (stream) => { - let lastStatus = ''; - - try { - // Stream updates until deployment completes or fails - while (true) { - const currentDeployment = deployments.get(deploymentId); - - if (!currentDeployment) { - await stream.writeSSE({ - event: 'error', - data: JSON.stringify({ message: 'Deployment not found' }) - }); - break; - } - - // Send update if status changed - const currentStatus = JSON.stringify({ - phase: currentDeployment.phase, - status: currentDeployment.status, - progress: currentDeployment.progress, - message: currentDeployment.message, - }); - - if (currentStatus !== lastStatus) { - await stream.writeSSE({ - event: 'progress', - data: JSON.stringify({ - phase: currentDeployment.phase, - status: currentDeployment.status, - progress: currentDeployment.progress, - message: currentDeployment.message, - currentStep: currentDeployment.message, // Backward compatibility - url: currentDeployment.url, - error: currentDeployment.error?.message, - resources: currentDeployment.resources, - }) - }); - lastStatus = currentStatus; - } - - // Exit if terminal state - if (currentDeployment.status === 'success' || currentDeployment.phase === 'completed') { - await stream.writeSSE({ - event: 'complete', - data: JSON.stringify({ - url: currentDeployment.url, - status: 'ready', - resources: currentDeployment.resources, - duration: currentDeployment.timestamps.completed && currentDeployment.timestamps.started - ? (new Date(currentDeployment.timestamps.completed).getTime() - - new Date(currentDeployment.timestamps.started).getTime()) / 1000 - : null, - }) - }); - break; - } - - if (currentDeployment.status === 'failure' || currentDeployment.phase === 'failed') { - await stream.writeSSE({ - event: 'error', - data: JSON.stringify({ - message: currentDeployment.error?.message || 'Deployment failed', - status: 'failed', - phase: currentDeployment.error?.phase, - code: currentDeployment.error?.code, - }) - }); - break; - } - - // Wait before next check - await stream.sleep(1000); - } - } catch (error) { - console.error('SSE stream error:', error); - } - }); -}); - -// Get deployment details (new endpoint for debugging) -app.get('/api/deployment/:deploymentId', (c) => { - const deploymentId = c.req.param('deploymentId'); - const deployment = deployments.get(deploymentId); - - if (!deployment) { - return c.json({ - success: false, - error: 'Deployment not found', - code: 'NOT_FOUND' - }, 404); - } - - return c.json({ - success: true, - deployment: { - id: deployment.id, - stackName: deployment.stackName, - phase: deployment.phase, - status: deployment.status, - progress: deployment.progress, - message: deployment.message, - url: deployment.url, - error: deployment.error, - resources: deployment.resources, - timestamps: deployment.timestamps, - logs: deployment.logs.slice(-50), // Last 50 log entries - } - }); -}); - -// Check name availability -app.get('/api/check/:name', async (c) => { - try { - const name = c.req.param('name'); - - // Validate name format - const validation = validateStackName(name); - if (!validation.valid) { - return c.json({ - available: false, - valid: false, - error: validation.error - }); - } - - const normalizedName = name.trim().toLowerCase(); - - // Check if project exists - const client = createProductionDokployClient(); - const projectName = `ai-stack-${normalizedName}`; - const existingProject = await client.findProjectByName(projectName); - - return c.json({ - available: !existingProject, - valid: true, - name: normalizedName - }); - - } catch (error) { - console.error('Check endpoint error:', error); - return c.json({ - available: false, - valid: false, - error: 'Failed to check availability' - }, 500); - } -}); - -// Serve static files (frontend) -app.use('/static/*', serveStatic({ root: './src/frontend' })); -app.use('/*', serveStatic({ root: './src/frontend', path: '/index.html' })); - -console.log(`🚀 AI Stack Deployer (Production) starting on http://${HOST}:${PORT}`); -console.log(`✅ Production features enabled:`); -console.log(` - Retry logic with exponential backoff`); -console.log(` - Circuit breaker pattern`); -console.log(` - Automatic rollback on failure`); -console.log(` - Health verification`); -console.log(` - Structured logging`); - -export default { - port: PORT, - hostname: HOST, - fetch: app.fetch, -}; diff --git a/src/test-api-formats.ts b/tests/test-api-formats.ts similarity index 100% rename from src/test-api-formats.ts rename to tests/test-api-formats.ts diff --git a/src/test-clients.ts b/tests/test-clients.ts similarity index 100% rename from src/test-clients.ts rename to tests/test-clients.ts diff --git a/src/test-deploy-persistent.ts b/tests/test-deploy-persistent.ts similarity index 100% rename from src/test-deploy-persistent.ts rename to tests/test-deploy-persistent.ts diff --git a/src/test-deployment-proof.ts b/tests/test-deployment-proof.ts similarity index 100% rename from src/test-deployment-proof.ts rename to tests/test-deployment-proof.ts diff --git a/src/test-production-deployment.ts b/tests/test-production-deployment.ts similarity index 100% rename from src/test-production-deployment.ts rename to tests/test-production-deployment.ts diff --git a/src/validation.test.ts b/tests/validation.test.ts similarity index 100% rename from src/validation.test.ts rename to tests/validation.test.ts