11package core
22
3- // This file contains logic related to post-generation cleansing / normalization
4- // of build plans. Keeping this separate from `core.go` keeps provider selection
5- // and config merge logic focused while allowing targeted tests for plan
6- // mutation behavior.
7-
83import (
94 "regexp"
105
11- "github.com/charmbracelet/log"
126 "github.com/railwayapp/railpack/core/logger"
137 "github.com/railwayapp/railpack/core/plan"
8+ "github.com/railwayapp/railpack/core/providers/node"
149)
1510
1611// Regexes for matching commands that intentionally remove node_modules or perform
1712// clean installs (which implicitly delete the directory) so we can avoid mounting
1813// the node_modules cache in those steps.
1914var (
20- npmCiCommandRegex = regexp .MustCompile (`.*npm\s+ci\b.*` )
21- removeNodeModulesRegex = regexp .MustCompile (`(^|\s)(rm\s+-rf\s+|rimraf\s+)(\./)?node_modules(\s|;|&|$)` )
22- )
15+ // Matches "npm ci" with flexible whitespace, using word boundaries
16+ npmCiCommandRegex = regexp .MustCompile (`(?i)\bnpm\s+ci\b` )
2317
24- const (
25- // NODE_MODULES_CACHE is the path inside the build container we treat as the
26- // node modules cache. Steps which delete node_modules should not mount this
27- // cache so the deletion stays local to the layer they're producing and does
28- // not wipe the shared cache directory.
29- NODE_MODULES_CACHE = "/app/node_modules/.cache"
18+ // Matches common delete commands targeting node_modules
19+ removeNodeModulesRegex = regexp .MustCompile (`(?i)\b(?:rm\s+-r[f]?|rmdir|rimraf)\s+(?:\S*\/)?node_modules\b` )
3020)
3121
3222// willRemoveNodeModules determines if any command in the provided slice removes
3323// the node_modules directory either directly (rm/rimraf) or indirectly (npm ci).
24+ // this is brittle & imperfect: https://github.com/railwayapp/railpack/pull/259
3425func willRemoveNodeModules (commands []plan.Command ) bool {
3526 for _ , cmd := range commands {
3627 if execCmd , ok := cmd .(plan.ExecCommand ); ok {
37- log .Debugf ("Inspecting build command: %s" , execCmd .Cmd )
3828 if npmCiCommandRegex .MatchString (execCmd .Cmd ) || removeNodeModulesRegex .MatchString (execCmd .Cmd ) {
3929 return true
4030 }
@@ -51,7 +41,7 @@ func cleansePlanStructure(buildPlan *plan.BuildPlan, logger *logger.Logger) {
5141 // let's get the cache key name that has a Directory of NODE_MODULES_CACHE
5242 var nodeModulesCacheKey string
5343 for cacheName , cacheDef := range buildPlan .Caches {
54- if cacheDef .Directory == NODE_MODULES_CACHE {
44+ if cacheDef .Directory == node . NODE_MODULES_CACHE {
5545 nodeModulesCacheKey = cacheName
5646 break
5747 }
@@ -74,6 +64,7 @@ func cleansePlanStructure(buildPlan *plan.BuildPlan, logger *logger.Logger) {
7464 continue
7565 }
7666
67+ // It's important that we do not result in an array with a zeroed string, which is why we are using this ugly loop
7768 var newCaches []string
7869 for _ , name := range step .Caches {
7970 if name != "" && name != nodeModulesCacheKey {
0 commit comments