package nodejs import ( "encoding/json" "fmt" "os" "path/filepath" "strings" ) type NodeJS struct { // Add fields as needed for Node.js analysis } // Name returns the name of the Node.js technology stack. func (n *NodeJS) Name() string { return "Node.js" } // Analyze analyzes the codebase and returns a guessed technology stack for Node.js. func (n *NodeJS) Analyze(codebasePath string) (bool, error) { // Implement the logic to analyze the Node.js codebase // This could involve checking for specific files, directories, or patterns // For example, checking for a Dockerfile, package.json, server.js, etc. // Return true if the analysis confirms it's a Node.js project // Placeholder implementation - adapt this to your actual analysis logic // For instance, check for package.json if _, err := os.Stat(filepath.Join(codebasePath, "package.json")); err == nil { return true, nil // It's a Node.js project } return false, nil // Not a Node.js project or error (handle error appropriately) } type NodeProjectAnalysis struct { NodeVersion string `json:"node_version"` PackageManager string `json:"package_manager"` Entrypoint string `json:"entrypoint"` Framework string `json:"framework"` Port int `json:"port"` BuildScript string `json:"build_script"` StartScript string `json:"start_script"` Dependencies []string `json:"dependencies"` DevDependencies []string `json:"dev_dependencies"` } func (n *NodeJS) analyzeNodeProject(codebasePath string) (*NodeProjectAnalysis, error) { analysis := &NodeProjectAnalysis{ Port: 3000, // Default for Node.js } // Read package.json packageJsonPath := filepath.Join(codebasePath, "package.json") content, err := os.ReadFile(packageJsonPath) if err != nil { return nil, fmt.Errorf("package.json not found: %w", err) } var packageJson struct { Main string `json:"main"` Scripts map[string]string `json:"scripts"` Dependencies map[string]string `json:"dependencies"` DevDependencies map[string]string `json:"devDependencies"` Engines map[string]string `json:"engines"` } if err := json.Unmarshal(content, &packageJson); err != nil { return nil, fmt.Errorf("failed to parse package.json: %w", err) } // Determine entrypoint analysis.Entrypoint = packageJson.Main if analysis.Entrypoint == "" { // Check common entry points entrypoints := []string{"server.js", "app.js", "index.js", "main.js", "src/index.js", "src/server.js"} for _, ep := range entrypoints { if n.fileExists(filepath.Join(codebasePath, ep)) { analysis.Entrypoint = ep break } } if analysis.Entrypoint == "" { analysis.Entrypoint = "index.js" // Default } } // Detect package manager if n.fileExists(filepath.Join(codebasePath, "yarn.lock")) { analysis.PackageManager = "yarn" } else if n.fileExists(filepath.Join(codebasePath, "pnpm-lock.yaml")) { analysis.PackageManager = "pnpm" } else { analysis.PackageManager = "npm" } // Get Node version from engines if nodeVersion, ok := packageJson.Engines["node"]; ok { // Extract version number (remove ^ and ~ prefixes) analysis.NodeVersion = strings.TrimLeft(nodeVersion, "^~>=") if strings.Contains(analysis.NodeVersion, ".") { parts := strings.Split(analysis.NodeVersion, ".") analysis.NodeVersion = parts[0] // Use major version only } } // Detect scripts if buildScript, ok := packageJson.Scripts["build"]; ok && buildScript != "" { analysis.BuildScript = "build" } if startScript, ok := packageJson.Scripts["start"]; ok && startScript != "" { analysis.StartScript = "start" } // Detect framework from dependencies for dep := range packageJson.Dependencies { switch { case strings.Contains(dep, "express"): analysis.Framework = "express" case strings.Contains(dep, "next"): analysis.Framework = "nextjs" case strings.Contains(dep, "react"): analysis.Framework = "react" case strings.Contains(dep, "vue"): analysis.Framework = "vue" case strings.Contains(dep, "angular"): analysis.Framework = "angular" case strings.Contains(dep, "fastify"): analysis.Framework = "fastify" case strings.Contains(dep, "koa"): analysis.Framework = "koa" } } // Extract dependency names for dep := range packageJson.Dependencies { analysis.Dependencies = append(analysis.Dependencies, dep) } return analysis, nil } func (n *NodeJS) fileExists(path string) bool { _, err := os.Stat(path) return err == nil } // GenerateDockerfile generates a Dockerfile for Node.js projects based on analysis func (n *NodeJS) GenerateDockerfile(codebasePath string) (string, error) { // Analyze the Node.js project analysis, err := n.analyzeNodeProject(codebasePath) if err != nil { return "", fmt.Errorf("failed to analyze Node.js project: %w", err) } nodeVersion := analysis.NodeVersion if nodeVersion == "" { nodeVersion = "18" } // Determine install command based on package manager var installCmd string var copyLockFile string switch analysis.PackageManager { case "yarn": installCmd = "yarn install --frozen-lockfile --production" copyLockFile = "COPY yarn.lock ./yarn.lock" case "pnpm": installCmd = "pnpm install --frozen-lockfile --prod" copyLockFile = "COPY pnpm-lock.yaml ./pnpm-lock.yaml" default: installCmd = "npm ci --only=production" copyLockFile = "COPY package-lock.json ./package-lock.json" } // Add build script if exists buildScript := "" if analysis.BuildScript != "" { switch analysis.PackageManager { case "yarn": buildScript = "RUN yarn build" case "pnpm": buildScript = "RUN pnpm build" default: buildScript = "RUN npm run build" } } dockerfile := fmt.Sprintf(`# Auto-generated Dockerfile for Node.js application # Generated by BYOP Engine - Node.js Stack Analyzer FROM node:%s-alpine # Set working directory WORKDIR /app # Copy package files for better caching COPY package.json ./ %s # Install dependencies RUN %s # Copy source code COPY . . # Build the application if build script exists %s # Create non-root user RUN addgroup -g 1001 -S nodejs && \ adduser -S nextjs -u 1001 # Change ownership RUN chown -R nextjs:nodejs /app USER nextjs # Expose port EXPOSE %d # Start the application CMD ["%s"] `, nodeVersion, copyLockFile, installCmd, buildScript, analysis.Port, analysis.StartScript, ) return dockerfile, nil }