UNPKG

@ordojs/cli

Version:

Command-line interface for OrdoJS framework

614 lines (502 loc) 12.4 kB
/** * @fileoverview OrdoJS CLI - Docker configuration generator */ import { CLIError, ErrorType } from '../index.js'; import type { DeploymentConfig } from './adapter-interface.js'; export interface DockerConfig { baseImage: string; nodeVersion: string; buildStage: boolean; productionStage: boolean; multiStage: boolean; port: number; healthCheck: boolean; environment: Record<string, string>; volumes: string[]; commands: string[]; } export interface DockerfileOptions { multiStage?: boolean; nodeVersion?: string; port?: number; healthCheck?: boolean; environment?: Record<string, string>; volumes?: string[]; commands?: string[]; } /** * Docker configuration generator for OrdoJS applications */ export class DockerGenerator { private defaultConfig: DockerConfig = { baseImage: 'node:18-alpine', nodeVersion: '18', buildStage: true, productionStage: true, multiStage: true, port: 3000, healthCheck: true, environment: {}, volumes: [], commands: [] }; /** * Generate Dockerfile content for an OrdoJS application */ generateDockerfile(config: DeploymentConfig, options: DockerfileOptions = {}): string { const dockerConfig = this.mergeConfig(options); try { if (dockerConfig.multiStage) { return this.generateMultiStageDockerfile(dockerConfig); } else { return this.generateSimpleDockerfile(dockerConfig); } } catch (error) { throw new CLIError( `Failed to generate Dockerfile: ${error instanceof Error ? error.message : String(error)}`, ErrorType.DEPLOYMENT, 'DOCKER-001' ); } } /** * Generate docker-compose.yml content */ generateDockerCompose(config: DeploymentConfig, options: DockerfileOptions = {}): string { const dockerConfig = this.mergeConfig(options); try { return this.generateDockerComposeContent(dockerConfig, config); } catch (error) { throw new CLIError( `Failed to generate docker-compose.yml: ${error instanceof Error ? error.message : String(error)}`, ErrorType.DEPLOYMENT, 'DOCKER-002' ); } } /** * Generate .dockerignore file content */ generateDockerignore(): string { return `# Dependencies node_modules npm-debug.log* yarn-debug.log* yarn-error.log* # Runtime data pids *.pid *.seed *.pid.lock # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage .grunt # Bower dependency directory bower_components # node-waf configuration .lock-wscript # Compiled binary addons build/Release # Dependency directories jspm_packages/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test .env.production # parcel-bundler cache .cache .parcel-cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ public # Storybook build outputs .out .storybook-out # Temporary folders tmp/ temp/ # Logs logs *.log # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # Grunt intermediate storage .grunt # Bower dependency directory bower_components # node-waf configuration .lock-wscript # Compiled binary addons build/Release # Dependency directories node_modules/ jspm_packages/ # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env # parcel-bundler cache .cache .parcel-cache # next.js build output .next # nuxt.js build output .nuxt # vuepress build output .vuepress/dist # Serverless directories .serverless # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port # Stores VSCode versions used for testing VSCode extensions .vscode-test # OrdoJS specific .ordo-cache build/ dist/ *.ordo.js *.ordo.css # IDE .vscode/ .idea/ *.swp *.swo *~ # OS .DS_Store Thumbs.db # Git .git/ .gitignore # Documentation docs/ *.md # Tests test/ tests/ __tests__/ *.test.js *.test.ts *.spec.js *.spec.ts # Development .dev/ dev/ `; } /** * Generate Kubernetes manifests */ generateKubernetesManifests(config: DeploymentConfig, options: DockerfileOptions = {}): { deployment: string; service: string; ingress?: string; configMap?: string; secret?: string; } { const dockerConfig = this.mergeConfig(options); try { return { deployment: this.generateK8sDeployment(dockerConfig, config), service: this.generateK8sService(dockerConfig, config), ingress: this.generateK8sIngress(dockerConfig, config), configMap: this.generateK8sConfigMap(dockerConfig, config), secret: this.generateK8sSecret(dockerConfig, config) }; } catch (error) { throw new CLIError( `Failed to generate Kubernetes manifests: ${error instanceof Error ? error.message : String(error)}`, ErrorType.DEPLOYMENT, 'DOCKER-003' ); } } private mergeConfig(options: DockerfileOptions): DockerConfig { return { ...this.defaultConfig, ...options, environment: { ...this.defaultConfig.environment, ...options.environment }, volumes: [...this.defaultConfig.volumes, ...(options.volumes || [])], commands: [...this.defaultConfig.commands, ...(options.commands || [])] }; } private generateMultiStageDockerfile(config: DockerConfig): string { return `# Multi-stage Dockerfile for OrdoJS application FROM node:${config.nodeVersion}-alpine AS base # Set working directory WORKDIR /app # Copy package files COPY package*.json ./ COPY pnpm-lock.yaml ./ # Install pnpm RUN npm install -g pnpm # Install dependencies RUN pnpm install --frozen-lockfile # Copy source code COPY . . # Build stage FROM base AS builder # Build the application RUN pnpm run build # Production stage FROM node:${config.nodeVersion}-alpine AS production # Create app user RUN addgroup -g 1001 -S nodejs RUN adduser -S ordojs -u 1001 # Set working directory WORKDIR /app # Copy package files COPY package*.json ./ COPY pnpm-lock.yaml ./ # Install pnpm and production dependencies only RUN npm install -g pnpm RUN pnpm install --frozen-lockfile --prod # Copy built application from builder stage COPY --from=builder --chown=ordojs:nodejs /app/dist ./dist COPY --from=builder --chown=ordojs:nodejs /app/public ./public # Switch to non-root user USER ordojs # Expose port EXPOSE ${config.port} # Set environment variables ${this.generateEnvironmentVariables(config.environment)} # Health check ${config.healthCheck ? this.generateHealthCheck(config.port) : ''} # Start the application CMD ["node", "dist/server.js"] `; } private generateSimpleDockerfile(config: DockerConfig): string { return `# Simple Dockerfile for OrdoJS application FROM node:${config.nodeVersion}-alpine # Set working directory WORKDIR /app # Copy package files COPY package*.json ./ COPY pnpm-lock.yaml ./ # Install pnpm RUN npm install -g pnpm # Install dependencies RUN pnpm install --frozen-lockfile # Copy source code COPY . . # Build the application RUN pnpm run build # Expose port EXPOSE ${config.port} # Set environment variables ${this.generateEnvironmentVariables(config.environment)} # Health check ${config.healthCheck ? this.generateHealthCheck(config.port) : ''} # Start the application CMD ["node", "dist/server.js"] `; } private generateDockerComposeContent(config: DockerConfig, deploymentConfig: DeploymentConfig): string { return `version: '3.8' services: ordojs-app: build: context: . dockerfile: Dockerfile ports: - "${config.port}:${config.port}" environment: ${this.generateDockerComposeEnvironment(config.environment)} volumes: ${this.generateDockerComposeVolumes(config.volumes)} restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:${config.port}/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s networks: default: name: ordojs-network `; } private generateK8sDeployment(config: DockerConfig, deploymentConfig: DeploymentConfig): string { return `apiVersion: apps/v1 kind: Deployment metadata: name: ordojs-app labels: app: ordojs-app spec: replicas: 3 selector: matchLabels: app: ordojs-app template: metadata: labels: app: ordojs-app spec: containers: - name: ordojs-app image: ordojs-app:latest ports: - containerPort: ${config.port} env: ${this.generateK8sEnvironment(config.environment)} resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "512Mi" cpu: "500m" livenessProbe: httpGet: path: /health port: ${config.port} initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health port: ${config.port} initialDelaySeconds: 5 periodSeconds: 5 `; } private generateK8sService(config: DockerConfig, deploymentConfig: DeploymentConfig): string { return `apiVersion: v1 kind: Service metadata: name: ordojs-app-service spec: selector: app: ordojs-app ports: - protocol: TCP port: 80 targetPort: ${config.port} type: ClusterIP `; } private generateK8sIngress(config: DockerConfig, deploymentConfig: DeploymentConfig): string { return `apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ordojs-app-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: ordojs-app.local http: paths: - path: / pathType: Prefix backend: service: name: ordojs-app-service port: number: 80 `; } private generateK8sConfigMap(config: DockerConfig, deploymentConfig: DeploymentConfig): string { const envVars = Object.entries(config.environment) .filter(([key]) => !key.toLowerCase().includes('secret') && !key.toLowerCase().includes('password')) .map(([key, value]) => ` ${key}: "${value}"`) .join('\n'); return `apiVersion: v1 kind: ConfigMap metadata: name: ordojs-app-config data: ${envVars} `; } private generateK8sSecret(config: DockerConfig, deploymentConfig: DeploymentConfig): string { const secretVars = Object.entries(config.environment) .filter(([key]) => key.toLowerCase().includes('secret') || key.toLowerCase().includes('password')) .map(([key, value]) => ` ${key}: ${Buffer.from(value).toString('base64')}`) .join('\n'); if (!secretVars) { return ''; } return `apiVersion: v1 kind: Secret metadata: name: ordojs-app-secret type: Opaque data: ${secretVars} `; } private generateEnvironmentVariables(environment: Record<string, string>): string { return Object.entries(environment) .map(([key, value]) => `ENV ${key}=${value}`) .join('\n'); } private generateHealthCheck(port: number): string { return `HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\ CMD curl -f http://localhost:${port}/health || exit 1`; } private generateDockerComposeEnvironment(environment: Record<string, string>): string { return Object.entries(environment) .map(([key, value]) => ` - ${key}=${value}`) .join('\n'); } private generateDockerComposeVolumes(volumes: string[]): string { return volumes .map(volume => ` - ${volume}`) .join('\n'); } private generateK8sEnvironment(environment: Record<string, string>): string { return Object.entries(environment) .map(([key, value]) => ` - name: ${key} value: "${value}"`) .join('\n'); } }