nodejs.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. package nodejs
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "os"
  6. "path/filepath"
  7. "strings"
  8. )
  9. type NodeJS struct {
  10. // Add fields as needed for Node.js analysis
  11. }
  12. // Name returns the name of the Node.js technology stack.
  13. func (n *NodeJS) Name() string {
  14. return "Node.js"
  15. }
  16. // Analyze analyzes the codebase and returns a guessed technology stack for Node.js.
  17. func (n *NodeJS) Analyze(codebasePath string) (bool, error) {
  18. // Implement the logic to analyze the Node.js codebase
  19. // This could involve checking for specific files, directories, or patterns
  20. // For example, checking for a Dockerfile, package.json, server.js, etc.
  21. // Return true if the analysis confirms it's a Node.js project
  22. // Placeholder implementation - adapt this to your actual analysis logic
  23. // For instance, check for package.json
  24. if _, err := os.Stat(filepath.Join(codebasePath, "package.json")); err == nil {
  25. return true, nil // It's a Node.js project
  26. }
  27. return false, nil // Not a Node.js project or error (handle error appropriately)
  28. }
  29. type NodeProjectAnalysis struct {
  30. NodeVersion string `json:"node_version"`
  31. PackageManager string `json:"package_manager"`
  32. Entrypoint string `json:"entrypoint"`
  33. Framework string `json:"framework"`
  34. Port int `json:"port"`
  35. BuildScript string `json:"build_script"`
  36. StartScript string `json:"start_script"`
  37. Dependencies []string `json:"dependencies"`
  38. DevDependencies []string `json:"dev_dependencies"`
  39. }
  40. func (n *NodeJS) analyzeNodeProject(codebasePath string) (*NodeProjectAnalysis, error) {
  41. analysis := &NodeProjectAnalysis{
  42. Port: 3000, // Default for Node.js
  43. }
  44. // Read package.json
  45. packageJsonPath := filepath.Join(codebasePath, "package.json")
  46. content, err := os.ReadFile(packageJsonPath)
  47. if err != nil {
  48. return nil, fmt.Errorf("package.json not found: %w", err)
  49. }
  50. var packageJson struct {
  51. Main string `json:"main"`
  52. Scripts map[string]string `json:"scripts"`
  53. Dependencies map[string]string `json:"dependencies"`
  54. DevDependencies map[string]string `json:"devDependencies"`
  55. Engines map[string]string `json:"engines"`
  56. }
  57. if err := json.Unmarshal(content, &packageJson); err != nil {
  58. return nil, fmt.Errorf("failed to parse package.json: %w", err)
  59. }
  60. // Determine entrypoint
  61. analysis.Entrypoint = packageJson.Main
  62. if analysis.Entrypoint == "" {
  63. // Check common entry points
  64. entrypoints := []string{"server.js", "app.js", "index.js", "main.js", "src/index.js", "src/server.js"}
  65. for _, ep := range entrypoints {
  66. if n.fileExists(filepath.Join(codebasePath, ep)) {
  67. analysis.Entrypoint = ep
  68. break
  69. }
  70. }
  71. if analysis.Entrypoint == "" {
  72. analysis.Entrypoint = "index.js" // Default
  73. }
  74. }
  75. // Detect package manager
  76. if n.fileExists(filepath.Join(codebasePath, "yarn.lock")) {
  77. analysis.PackageManager = "yarn"
  78. } else if n.fileExists(filepath.Join(codebasePath, "pnpm-lock.yaml")) {
  79. analysis.PackageManager = "pnpm"
  80. } else {
  81. analysis.PackageManager = "npm"
  82. }
  83. // Get Node version from engines
  84. if nodeVersion, ok := packageJson.Engines["node"]; ok {
  85. // Extract version number (remove ^ and ~ prefixes)
  86. analysis.NodeVersion = strings.TrimLeft(nodeVersion, "^~>=")
  87. if strings.Contains(analysis.NodeVersion, ".") {
  88. parts := strings.Split(analysis.NodeVersion, ".")
  89. analysis.NodeVersion = parts[0] // Use major version only
  90. }
  91. }
  92. // Detect scripts
  93. if buildScript, ok := packageJson.Scripts["build"]; ok && buildScript != "" {
  94. analysis.BuildScript = "build"
  95. }
  96. if startScript, ok := packageJson.Scripts["start"]; ok && startScript != "" {
  97. analysis.StartScript = "start"
  98. }
  99. // Detect framework from dependencies
  100. for dep := range packageJson.Dependencies {
  101. switch {
  102. case strings.Contains(dep, "express"):
  103. analysis.Framework = "express"
  104. case strings.Contains(dep, "next"):
  105. analysis.Framework = "nextjs"
  106. case strings.Contains(dep, "react"):
  107. analysis.Framework = "react"
  108. case strings.Contains(dep, "vue"):
  109. analysis.Framework = "vue"
  110. case strings.Contains(dep, "angular"):
  111. analysis.Framework = "angular"
  112. case strings.Contains(dep, "fastify"):
  113. analysis.Framework = "fastify"
  114. case strings.Contains(dep, "koa"):
  115. analysis.Framework = "koa"
  116. }
  117. }
  118. // Extract dependency names
  119. for dep := range packageJson.Dependencies {
  120. analysis.Dependencies = append(analysis.Dependencies, dep)
  121. }
  122. return analysis, nil
  123. }
  124. func (n *NodeJS) fileExists(path string) bool {
  125. _, err := os.Stat(path)
  126. return err == nil
  127. }
  128. // GenerateDockerfile generates a Dockerfile for Node.js projects based on analysis
  129. func (n *NodeJS) GenerateDockerfile(codebasePath string) (string, error) {
  130. // Analyze the Node.js project
  131. analysis, err := n.analyzeNodeProject(codebasePath)
  132. if err != nil {
  133. return "", fmt.Errorf("failed to analyze Node.js project: %w", err)
  134. }
  135. nodeVersion := analysis.NodeVersion
  136. if nodeVersion == "" {
  137. nodeVersion = "18"
  138. }
  139. // Determine install command based on package manager
  140. var installCmd string
  141. var copyLockFile string
  142. switch analysis.PackageManager {
  143. case "yarn":
  144. installCmd = "yarn install --frozen-lockfile --production"
  145. copyLockFile = "COPY yarn.lock ./yarn.lock"
  146. case "pnpm":
  147. installCmd = "pnpm install --frozen-lockfile --prod"
  148. copyLockFile = "COPY pnpm-lock.yaml ./pnpm-lock.yaml"
  149. default:
  150. installCmd = "npm ci --only=production"
  151. copyLockFile = "COPY package-lock.json ./package-lock.json"
  152. }
  153. // Add build script if exists
  154. buildScript := ""
  155. if analysis.BuildScript != "" {
  156. switch analysis.PackageManager {
  157. case "yarn":
  158. buildScript = "RUN yarn build"
  159. case "pnpm":
  160. buildScript = "RUN pnpm build"
  161. default:
  162. buildScript = "RUN npm run build"
  163. }
  164. }
  165. dockerfile := fmt.Sprintf(`# Auto-generated Dockerfile for Node.js application
  166. # Generated by BYOP Engine - Node.js Stack Analyzer
  167. FROM node:%s-alpine
  168. # Set working directory
  169. WORKDIR /app
  170. # Copy package files for better caching
  171. COPY package.json ./
  172. %s
  173. # Install dependencies
  174. RUN %s
  175. # Copy source code
  176. COPY . .
  177. # Build the application if build script exists
  178. %s
  179. # Create non-root user
  180. RUN addgroup -g 1001 -S nodejs && \
  181. adduser -S nextjs -u 1001
  182. # Change ownership
  183. RUN chown -R nextjs:nodejs /app
  184. USER nextjs
  185. # Expose port
  186. EXPOSE %d
  187. # Start the application
  188. CMD ["%s"]
  189. `,
  190. nodeVersion,
  191. copyLockFile,
  192. installCmd,
  193. buildScript,
  194. analysis.Port,
  195. analysis.StartScript,
  196. )
  197. return dockerfile, nil
  198. }