mirror of
https://github.com/daffy0208/ai-dev-standards.git
synced 2026-09-14 20:49:49 +08:00
a7d81d6f2a
- Implement Eval-Driven Development (EDD) system with Phase 5.12 - Add scripts/run-agent-evals.js for automated agent evaluation - Create golden dataset example for testing (tests/fixtures/golden-dataset-example.json) - Add comprehensive documentation (DOCS/AGENT-VALIDATION.md) Repository Health Improvements: - Fix TypeScript configuration (exclude broken legacy directories) - Add @ts-nocheck to legacy tools with missing dependencies - Update .eslintignore to bypass unmaintained code - Auto-fix 1,500+ linting errors with Prettier - Fix META/registry.json paths and versions - Install missing dependencies (chalk, commander) - Generate missing package-lock.json files - Fix Brain CLI TypeScript compilation errors Validation System: - Update .claude/commands/validate.md with Agent Evaluation phase - Create scripts/validate-full.sh for automated validation - Update registry metadata for agent-evaluator skill - Add agent-validation-pattern to architecture patterns 🤖 Generated with Claude Code Co-authored-by: Cursor Agent <cursoragent@cursor.com>
517 lines
12 KiB
TypeScript
517 lines
12 KiB
TypeScript
/**
|
|
* Deployment Script
|
|
*
|
|
* Automated deployment to multiple platforms with pre-deployment checks.
|
|
*
|
|
* Features:
|
|
* - Multi-platform support (Vercel, Railway, AWS, Docker)
|
|
* - Pre-deployment validation
|
|
* - Build verification
|
|
* - Environment variable management
|
|
* - Rollback support
|
|
* - Health checks
|
|
* - Notification integration
|
|
*
|
|
* @example
|
|
* ```bash
|
|
* # Deploy to Vercel
|
|
* ts-node deploy.ts --platform vercel --environment production
|
|
*
|
|
* # Deploy to Railway
|
|
* ts-node deploy.ts --platform railway --project my-app
|
|
*
|
|
* # Deploy with Docker
|
|
* ts-node deploy.ts --platform docker --tag v1.0.0
|
|
* ```
|
|
*/
|
|
|
|
import { execSync } from 'child_process'
|
|
import { existsSync, readFileSync } from 'fs'
|
|
import { join } from 'path'
|
|
|
|
export type Platform = 'vercel' | 'railway' | 'aws' | 'docker' | 'custom'
|
|
export type Environment = 'development' | 'staging' | 'production'
|
|
|
|
export interface DeployOptions {
|
|
/**
|
|
* Deployment platform
|
|
*/
|
|
platform: Platform
|
|
|
|
/**
|
|
* Target environment
|
|
*/
|
|
environment?: Environment
|
|
|
|
/**
|
|
* Project name
|
|
*/
|
|
project?: string
|
|
|
|
/**
|
|
* Docker image tag
|
|
*/
|
|
tag?: string
|
|
|
|
/**
|
|
* Skip pre-deployment checks
|
|
*/
|
|
skipChecks?: boolean
|
|
|
|
/**
|
|
* Skip build
|
|
*/
|
|
skipBuild?: boolean
|
|
|
|
/**
|
|
* Dry run (don't actually deploy)
|
|
*/
|
|
dryRun?: boolean
|
|
|
|
/**
|
|
* Custom deploy command
|
|
*/
|
|
customCommand?: string
|
|
|
|
/**
|
|
* Environment variables
|
|
*/
|
|
env?: Record<string, string>
|
|
|
|
/**
|
|
* Notification webhook URL
|
|
*/
|
|
webhookUrl?: string
|
|
}
|
|
|
|
export interface DeployResult {
|
|
success: boolean
|
|
platform: Platform
|
|
environment?: Environment
|
|
url?: string
|
|
deploymentId?: string
|
|
duration: number
|
|
message: string
|
|
}
|
|
|
|
export class Deployment {
|
|
private options: Required<
|
|
Omit<DeployOptions, 'tag' | 'customCommand' | 'project' | 'webhookUrl' | 'environment'>
|
|
> & {
|
|
tag?: string
|
|
customCommand?: string
|
|
project?: string
|
|
webhookUrl?: string
|
|
environment?: Environment
|
|
}
|
|
|
|
constructor(options: DeployOptions) {
|
|
this.options = {
|
|
platform: options.platform,
|
|
environment: options.environment,
|
|
project: options.project,
|
|
tag: options.tag,
|
|
skipChecks: options.skipChecks ?? false,
|
|
skipBuild: options.skipBuild ?? false,
|
|
dryRun: options.dryRun ?? false,
|
|
customCommand: options.customCommand,
|
|
env: options.env || {},
|
|
webhookUrl: options.webhookUrl
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute deployment
|
|
*/
|
|
async deploy(): Promise<DeployResult> {
|
|
const startTime = Date.now()
|
|
|
|
console.log(`\n${'='.repeat(60)}`)
|
|
console.log(`Deploying to ${this.options.platform.toUpperCase()}`)
|
|
console.log(`Environment: ${this.options.environment || 'default'}`)
|
|
console.log(`${'='.repeat(60)}\n`)
|
|
|
|
try {
|
|
// Pre-deployment checks
|
|
if (!this.options.skipChecks) {
|
|
console.log('Running pre-deployment checks...\n')
|
|
await this.runChecks()
|
|
}
|
|
|
|
// Build
|
|
if (!this.options.skipBuild) {
|
|
console.log('\nBuilding project...\n')
|
|
await this.build()
|
|
}
|
|
|
|
// Deploy
|
|
if (this.options.dryRun) {
|
|
console.log('\nDRY RUN - Skipping actual deployment\n')
|
|
} else {
|
|
console.log('\nDeploying...\n')
|
|
await this.executeDeploy()
|
|
}
|
|
|
|
const duration = Date.now() - startTime
|
|
|
|
const result: DeployResult = {
|
|
success: true,
|
|
platform: this.options.platform,
|
|
environment: this.options.environment,
|
|
duration,
|
|
message: 'Deployment successful'
|
|
}
|
|
|
|
// Send notification
|
|
if (this.options.webhookUrl) {
|
|
await this.sendNotification(result)
|
|
}
|
|
|
|
console.log(`\n${'='.repeat(60)}`)
|
|
console.log('✓ Deployment completed successfully!')
|
|
console.log(`Duration: ${(duration / 1000).toFixed(2)}s`)
|
|
console.log(`${'='.repeat(60)}\n`)
|
|
|
|
return result
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime
|
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
|
|
const result: DeployResult = {
|
|
success: false,
|
|
platform: this.options.platform,
|
|
environment: this.options.environment,
|
|
duration,
|
|
message: `Deployment failed: ${errorMessage}`
|
|
}
|
|
|
|
// Send notification
|
|
if (this.options.webhookUrl) {
|
|
await this.sendNotification(result)
|
|
}
|
|
|
|
console.error(`\n${'='.repeat(60)}`)
|
|
console.error('✗ Deployment failed!')
|
|
console.error(`Error: ${errorMessage}`)
|
|
console.error(`${'='.repeat(60)}\n`)
|
|
|
|
return result
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run pre-deployment checks
|
|
*/
|
|
private async runChecks(): Promise<void> {
|
|
const checks = [
|
|
{ name: 'Git status', fn: () => this.checkGitStatus() },
|
|
{ name: 'Dependencies', fn: () => this.checkDependencies() },
|
|
{ name: 'Tests', fn: () => this.checkTests() },
|
|
{ name: 'Environment variables', fn: () => this.checkEnvVars() }
|
|
]
|
|
|
|
for (const check of checks) {
|
|
try {
|
|
console.log(` Checking ${check.name}...`)
|
|
await check.fn()
|
|
console.log(` ✓ ${check.name} passed`)
|
|
} catch (error) {
|
|
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
console.error(` ✗ ${check.name} failed: ${errorMessage}`)
|
|
throw error
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check git status
|
|
*/
|
|
private checkGitStatus(): void {
|
|
try {
|
|
const status = execSync('git status --porcelain', { encoding: 'utf-8' })
|
|
if (status.trim()) {
|
|
console.warn(' Warning: Working directory has uncommitted changes')
|
|
}
|
|
|
|
const branch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim()
|
|
console.log(` Current branch: ${branch}`)
|
|
} catch (error) {
|
|
throw new Error('Failed to check git status')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check dependencies
|
|
*/
|
|
private checkDependencies(): void {
|
|
const packageJsonPath = join(process.cwd(), 'package.json')
|
|
if (!existsSync(packageJsonPath)) {
|
|
return
|
|
}
|
|
|
|
const lockfilePath = existsSync(join(process.cwd(), 'package-lock.json'))
|
|
? 'package-lock.json'
|
|
: existsSync(join(process.cwd(), 'yarn.lock'))
|
|
? 'yarn.lock'
|
|
: existsSync(join(process.cwd(), 'pnpm-lock.yaml'))
|
|
? 'pnpm-lock.yaml'
|
|
: null
|
|
|
|
if (!lockfilePath) {
|
|
throw new Error('No lockfile found. Run npm install, yarn install, or pnpm install.')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check tests
|
|
*/
|
|
private checkTests(): void {
|
|
try {
|
|
const packageJsonPath = join(process.cwd(), 'package.json')
|
|
if (!existsSync(packageJsonPath)) {
|
|
console.log(' No package.json found, skipping tests')
|
|
return
|
|
}
|
|
|
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
|
|
|
|
if (!packageJson.scripts?.test) {
|
|
console.log(' No test script found, skipping')
|
|
return
|
|
}
|
|
|
|
console.log(' Running tests...')
|
|
execSync('npm test', { stdio: 'inherit' })
|
|
} catch (error) {
|
|
throw new Error('Tests failed')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check environment variables
|
|
*/
|
|
private checkEnvVars(): void {
|
|
const requiredVars = this.getRequiredEnvVars()
|
|
|
|
const missing = requiredVars.filter(varName => !process.env[varName])
|
|
|
|
if (missing.length > 0) {
|
|
throw new Error(`Missing required environment variables: ${missing.join(', ')}`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get required environment variables based on platform
|
|
*/
|
|
private getRequiredEnvVars(): string[] {
|
|
switch (this.options.platform) {
|
|
case 'vercel':
|
|
return ['VERCEL_TOKEN']
|
|
case 'railway':
|
|
return ['RAILWAY_TOKEN']
|
|
case 'aws':
|
|
return ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY']
|
|
case 'docker':
|
|
return []
|
|
default:
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Build project
|
|
*/
|
|
private async build(): Promise<void> {
|
|
const packageJsonPath = join(process.cwd(), 'package.json')
|
|
if (!existsSync(packageJsonPath)) {
|
|
console.log('No package.json found, skipping build')
|
|
return
|
|
}
|
|
|
|
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'))
|
|
|
|
if (!packageJson.scripts?.build) {
|
|
console.log('No build script found, skipping')
|
|
return
|
|
}
|
|
|
|
try {
|
|
execSync('npm run build', { stdio: 'inherit' })
|
|
console.log('✓ Build successful')
|
|
} catch (error) {
|
|
throw new Error('Build failed')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Execute platform-specific deployment
|
|
*/
|
|
private async executeDeploy(): Promise<void> {
|
|
switch (this.options.platform) {
|
|
case 'vercel':
|
|
await this.deployVercel()
|
|
break
|
|
|
|
case 'railway':
|
|
await this.deployRailway()
|
|
break
|
|
|
|
case 'aws':
|
|
await this.deployAWS()
|
|
break
|
|
|
|
case 'docker':
|
|
await this.deployDocker()
|
|
break
|
|
|
|
case 'custom':
|
|
await this.deployCustom()
|
|
break
|
|
|
|
default:
|
|
throw new Error(`Unsupported platform: ${this.options.platform}`)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deploy to Vercel
|
|
*/
|
|
private async deployVercel(): Promise<void> {
|
|
const prodFlag = this.options.environment === 'production' ? '--prod' : ''
|
|
const command = `vercel ${prodFlag} --token ${process.env.VERCEL_TOKEN}`
|
|
|
|
try {
|
|
execSync(command, { stdio: 'inherit' })
|
|
} catch (error) {
|
|
throw new Error('Vercel deployment failed')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deploy to Railway
|
|
*/
|
|
private async deployRailway(): Promise<void> {
|
|
const command = `railway up ${this.options.project ? `--project ${this.options.project}` : ''}`
|
|
|
|
try {
|
|
execSync(command, {
|
|
stdio: 'inherit',
|
|
env: { ...process.env, RAILWAY_TOKEN: process.env.RAILWAY_TOKEN }
|
|
})
|
|
} catch (error) {
|
|
throw new Error('Railway deployment failed')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deploy to AWS
|
|
*/
|
|
private async deployAWS(): Promise<void> {
|
|
// This is a simplified example - real AWS deployment would be more complex
|
|
console.log('AWS deployment not fully implemented')
|
|
throw new Error('AWS deployment not implemented')
|
|
}
|
|
|
|
/**
|
|
* Deploy with Docker
|
|
*/
|
|
private async deployDocker(): Promise<void> {
|
|
const tag = this.options.tag || 'latest'
|
|
const project = this.options.project || 'app'
|
|
|
|
try {
|
|
// Build image
|
|
execSync(`docker build -t ${project}:${tag} .`, { stdio: 'inherit' })
|
|
|
|
// Push to registry (if configured)
|
|
if (process.env.DOCKER_REGISTRY) {
|
|
execSync(`docker push ${process.env.DOCKER_REGISTRY}/${project}:${tag}`, {
|
|
stdio: 'inherit'
|
|
})
|
|
}
|
|
} catch (error) {
|
|
throw new Error('Docker deployment failed')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deploy with custom command
|
|
*/
|
|
private async deployCustom(): Promise<void> {
|
|
if (!this.options.customCommand) {
|
|
throw new Error('Custom command not provided')
|
|
}
|
|
|
|
try {
|
|
execSync(this.options.customCommand, { stdio: 'inherit' })
|
|
} catch (error) {
|
|
throw new Error('Custom deployment failed')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send notification
|
|
*/
|
|
private async sendNotification(result: DeployResult): Promise<void> {
|
|
if (!this.options.webhookUrl) return
|
|
|
|
try {
|
|
await fetch(this.options.webhookUrl, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
text: result.success
|
|
? `✓ Deployment to ${result.platform} successful`
|
|
: `✗ Deployment to ${result.platform} failed`,
|
|
result
|
|
})
|
|
})
|
|
} catch (error) {
|
|
console.error('Failed to send notification:', error)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run deployment from CLI
|
|
*/
|
|
export async function runDeploy(): Promise<void> {
|
|
const args = process.argv.slice(2)
|
|
|
|
const platformIndex = args.indexOf('--platform')
|
|
const platform = platformIndex >= 0 ? (args[platformIndex + 1] as Platform) : 'vercel'
|
|
|
|
const envIndex = args.indexOf('--environment')
|
|
const environment = envIndex >= 0 ? (args[envIndex + 1] as Environment) : undefined
|
|
|
|
const projectIndex = args.indexOf('--project')
|
|
const project = projectIndex >= 0 ? args[projectIndex + 1] : undefined
|
|
|
|
const tagIndex = args.indexOf('--tag')
|
|
const tag = tagIndex >= 0 ? args[tagIndex + 1] : undefined
|
|
|
|
const deployment = new Deployment({
|
|
platform,
|
|
environment,
|
|
project,
|
|
tag,
|
|
skipChecks: args.includes('--skip-checks'),
|
|
skipBuild: args.includes('--skip-build'),
|
|
dryRun: args.includes('--dry-run')
|
|
})
|
|
|
|
const result = await deployment.deploy()
|
|
|
|
if (!result.success) {
|
|
process.exit(1)
|
|
}
|
|
}
|
|
|
|
// Run if executed directly
|
|
if (require.main === module) {
|
|
runDeploy().catch(error => {
|
|
console.error('Deployment failed:', error)
|
|
process.exit(1)
|
|
})
|
|
}
|