UNPKG

@foxframework/core

Version:

A modern, production-ready web framework for TypeScript/Node.js with modular routing, integrated template engine, CLI tools, and enterprise features

419 lines (359 loc) 12.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DockerfileGenerator = void 0; const base_generator_1 = require("./base.generator"); const path_1 = __importDefault(require("path")); const promises_1 = __importDefault(require("fs/promises")); class DockerfileGenerator extends base_generator_1.BaseGenerator { constructor() { super(...arguments); this.name = 'dockerfile'; this.description = 'Generate optimized Dockerfiles with multi-stage builds'; } async generate(context) { const { options, projectRoot } = context; const config = this.getDockerConfig(context); const files = []; // Generate main Dockerfile const dockerfile = await this.generateDockerfile(config, options); files.push({ path: path_1.default.join(projectRoot, 'Dockerfile'), content: dockerfile, action: 'create' }); // Generate .dockerignore const dockerignore = await this.generateDockerignore(); files.push({ path: path_1.default.join(projectRoot, '.dockerignore'), content: dockerignore, action: 'create' }); // Generate multi-stage Dockerfile if requested if (options.multistage) { const multistageDockerfile = await this.generateMultistageDockerfile(config, options); files.push({ path: path_1.default.join(projectRoot, 'Dockerfile.multistage'), content: multistageDockerfile, action: 'create' }); } // Generate development Dockerfile const devDockerfile = await this.generateDevelopmentDockerfile(config, options); files.push({ path: path_1.default.join(projectRoot, 'Dockerfile.dev'), content: devDockerfile, action: 'create' }); return files; } async generateDockerfile(config, options) { const baseImage = options.alpine ? 'node:18-alpine' : 'node:18-slim'; const packageManager = await this.detectPackageManager(); const hasTypeScript = await this.hasTypeScript(); return `# Production Dockerfile for Fox Framework Application FROM ${baseImage} # Install dumb-init for proper signal handling RUN apk add --no-cache dumb-init || apt-get update && apt-get install -y dumb-init # Create app directory and user WORKDIR ${config.workdir} RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 ${config.user || 'foxuser'} # Copy package files COPY package*.json ./ ${packageManager === 'yarn' ? 'COPY yarn.lock ./' : ''} ${packageManager === 'pnpm' ? 'COPY pnpm-lock.yaml ./' : ''} # Install dependencies RUN ${this.getInstallCommand(packageManager, 'production')} # Copy source code COPY --chown=${config.user || 'foxuser'}:nodejs . . ${hasTypeScript ? `# Build TypeScript RUN ${this.getBuildCommand(packageManager)}` : ''} # Set environment variables ${Object.entries(config.environment || {}) .map(([key, value]) => `ENV ${key}=${value}`) .join('\n')} # Expose port EXPOSE ${config.port} # Health check ${this.generateHealthCheckInstruction(config.healthCheck)} # Switch to non-root user USER ${config.user || 'foxuser'} # Use dumb-init to handle signals properly ENTRYPOINT ["dumb-init", "--"] # Start the application CMD ["${this.getStartCommand(packageManager)}"] `; } async generateMultistageDockerfile(config, options) { const baseImage = options.alpine ? 'node:18-alpine' : 'node:18-slim'; const packageManager = await this.detectPackageManager(); const hasTypeScript = await this.hasTypeScript(); return `# Multi-stage Dockerfile for Fox Framework Application ################### # BUILD STAGE ################### FROM ${baseImage} AS builder # Install build dependencies RUN apk add --no-cache python3 make g++ || apt-get update && apt-get install -y python3 make g++ WORKDIR /app # Copy package files COPY package*.json ./ ${packageManager === 'yarn' ? 'COPY yarn.lock ./' : ''} ${packageManager === 'pnpm' ? 'COPY pnpm-lock.yaml ./' : ''} # Install all dependencies (including dev dependencies) RUN ${this.getInstallCommand(packageManager, 'all')} # Copy source code COPY . . ${hasTypeScript ? `# Build TypeScript RUN ${this.getBuildCommand(packageManager)}` : ''} # Run tests (optional, comment out for faster builds) # RUN ${this.getTestCommand(packageManager)} # Clean dev dependencies RUN ${this.getCleanCommand(packageManager)} ################### # PRODUCTION STAGE ################### FROM ${baseImage} AS production # Install dumb-init for proper signal handling RUN apk add --no-cache dumb-init || apt-get update && apt-get install -y dumb-init # Create app directory and user WORKDIR ${config.workdir} RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 ${config.user || 'foxuser'} # Copy package files and install production dependencies COPY package*.json ./ ${packageManager === 'yarn' ? 'COPY yarn.lock ./' : ''} ${packageManager === 'pnpm' ? 'COPY pnpm-lock.yaml ./' : ''} RUN ${this.getInstallCommand(packageManager, 'production')} # Copy built application from builder stage COPY --from=builder --chown=${config.user || 'foxuser'}:nodejs /app/${hasTypeScript ? 'dist' : 'src'} ./${hasTypeScript ? 'dist' : 'src'} COPY --from=builder --chown=${config.user || 'foxuser'}:nodejs /app/package.json ./ # Set environment variables ${Object.entries(config.environment || {}) .map(([key, value]) => `ENV ${key}=${value}`) .join('\n')} # Expose port EXPOSE ${config.port} # Health check ${this.generateHealthCheckInstruction(config.healthCheck)} # Switch to non-root user USER ${config.user || 'foxuser'} # Use dumb-init to handle signals properly ENTRYPOINT ["dumb-init", "--"] # Start the application CMD ["${this.getStartCommand(packageManager)}"] ################### # DEVELOPMENT STAGE ################### FROM ${baseImage} AS development # Install development tools RUN apk add --no-cache bash curl || apt-get update && apt-get install -y bash curl WORKDIR ${config.workdir} # Copy package files COPY package*.json ./ ${packageManager === 'yarn' ? 'COPY yarn.lock ./' : ''} ${packageManager === 'pnpm' ? 'COPY pnpm-lock.yaml ./' : ''} # Install all dependencies RUN ${this.getInstallCommand(packageManager, 'all')} # Expose port EXPOSE ${config.port} # Development environment variables ENV NODE_ENV=development # Start in development mode CMD ["${this.getDevCommand(packageManager)}"] `; } async generateDevelopmentDockerfile(config, options) { const baseImage = options.alpine ? 'node:18-alpine' : 'node:18-slim'; const packageManager = await this.detectPackageManager(); return `# Development Dockerfile for Fox Framework Application FROM ${baseImage} # Install development tools and dependencies RUN apk add --no-cache bash curl git || apt-get update && apt-get install -y bash curl git WORKDIR ${config.workdir} # Copy package files COPY package*.json ./ ${packageManager === 'yarn' ? 'COPY yarn.lock ./' : ''} ${packageManager === 'pnpm' ? 'COPY pnpm-lock.yaml ./' : ''} # Install all dependencies (including dev dependencies) RUN ${this.getInstallCommand(packageManager, 'all')} # Expose port EXPOSE ${config.port} # Set development environment ENV NODE_ENV=development # Create volume mount points VOLUME ["/app/src", "/app/node_modules"] # Health check for development HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \\ CMD curl -f http://localhost:${config.port}/health || exit 1 # Start in development mode with hot reload CMD ["${this.getDevCommand(packageManager)}"] `; } async generateDockerignore() { return `# Dependencies node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* .pnpm-debug.log* # Development files *.log .env* !.env.example # Build output dist/ build/ .next/ out/ # IDE files .vscode/ .idea/ *.swp *.swo *~ # OS files .DS_Store Thumbs.db # Git .git/ .gitignore # Docker Dockerfile* docker-compose*.yml .dockerignore # Tests and coverage coverage/ .nyc_output/ __tests__/ *.test.ts *.test.js *.spec.ts *.spec.js # Documentation README.md LICENSE docs/ *.md # Misc .github/ .dev/ examples/ `; } generateHealthCheckInstruction(healthCheck) { if (!healthCheck) { return 'HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \\\n CMD curl -f http://localhost:3000/health || exit 1'; } const test = Array.isArray(healthCheck.test) ? healthCheck.test.join(' ') : healthCheck.test; return `HEALTHCHECK --interval=${healthCheck.interval} --timeout=${healthCheck.timeout} --start-period=${healthCheck.start_period || '40s'} --retries=${healthCheck.retries} \\ CMD ${test}`; } async detectPackageManager() { try { await promises_1.default.access('yarn.lock'); return 'yarn'; } catch { } try { await promises_1.default.access('pnpm-lock.yaml'); return 'pnpm'; } catch { } return 'npm'; } getInstallCommand(packageManager, type) { const commands = { npm: type === 'production' ? 'npm ci --only=production' : 'npm ci', yarn: type === 'production' ? 'yarn install --frozen-lockfile --production' : 'yarn install --frozen-lockfile', pnpm: type === 'production' ? 'pnpm install --frozen-lockfile --prod' : 'pnpm install --frozen-lockfile' }; return commands[packageManager] || commands.npm; } getBuildCommand(packageManager) { const commands = { npm: 'npm run build', yarn: 'yarn build', pnpm: 'pnpm build' }; return commands[packageManager] || commands.npm; } getStartCommand(packageManager) { const commands = { npm: 'npm start', yarn: 'yarn start', pnpm: 'pnpm start' }; return commands[packageManager] || commands.npm; } getDevCommand(packageManager) { const commands = { npm: 'npm run dev', yarn: 'yarn dev', pnpm: 'pnpm dev' }; return commands[packageManager] || commands.npm; } getTestCommand(packageManager) { const commands = { npm: 'npm test', yarn: 'yarn test', pnpm: 'pnpm test' }; return commands[packageManager] || commands.npm; } getCleanCommand(packageManager) { const commands = { npm: 'npm prune --production', yarn: 'yarn install --production --ignore-scripts --prefer-offline', pnpm: 'pnpm prune --prod' }; return commands[packageManager] || commands.npm; } async hasTypeScript() { try { await promises_1.default.access('tsconfig.json'); return true; } catch { return false; } } getDockerConfig(context) { const packageJson = this.readPackageJson(context.projectRoot); return { baseImage: 'node:18-alpine', nodeVersion: '18', workdir: '/app', port: 3000, healthCheck: { test: ['CMD', 'curl', '-f', 'http://localhost:3000/health'], interval: '30s', timeout: '10s', retries: 3, start_period: '40s' }, user: 'foxuser', environment: { NODE_ENV: 'production', PORT: '3000' } }; } readPackageJson(projectRoot) { try { const packagePath = path_1.default.join(projectRoot, 'package.json'); const content = require('fs').readFileSync(packagePath, 'utf8'); return JSON.parse(content); } catch { return { name: 'fox-app', version: '1.0.0' }; } } } exports.DockerfileGenerator = DockerfileGenerator;