From 67172e29dcf410c83bc660f1738d102810a77597 Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Wed, 29 Nov 2023 17:05:49 +0100 Subject: [PATCH] feat: add yarn berry support --- src/nodejs/supply/mocks_test.go | 14 +++++ src/nodejs/supply/supply.go | 21 +++++-- src/nodejs/yarn/yarn.go | 106 +++++++++++++++++++++++++++++++- 3 files changed, 134 insertions(+), 7 deletions(-) diff --git a/src/nodejs/supply/mocks_test.go b/src/nodejs/supply/mocks_test.go index dd78457ec..7bb89ce31 100644 --- a/src/nodejs/supply/mocks_test.go +++ b/src/nodejs/supply/mocks_test.go @@ -245,6 +245,20 @@ func (mr *MockYarnMockRecorder) Build(arg0, arg1 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockYarn)(nil).Build), arg0, arg1) } +// Rebuild mocks base method. +func (m *MockYarn) Rebuild(arg0, arg1 string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Rebuild", arg0) + ret0, _ := ret[0].(error) + return ret0 +} + +// Rebuild indicates an expected call of Rebuild. +func (mr *MockYarnMockRecorder) Rebuild(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Rebuild", reflect.TypeOf((*MockNPM)(nil).Rebuild), arg0) +} + // MockStager is a mock of Stager interface. type MockStager struct { ctrl *gomock.Controller diff --git a/src/nodejs/supply/supply.go b/src/nodejs/supply/supply.go index bcc3e4cab..4a136918f 100644 --- a/src/nodejs/supply/supply.go +++ b/src/nodejs/supply/supply.go @@ -46,6 +46,7 @@ type NPM interface { type Yarn interface { Build(string, string) error + Rebuild(string, string) error } type Stager interface { @@ -315,18 +316,26 @@ func (s *Supplier) BuildDependencies() error { return err } + if (s.IsVendored) { + s.Log.Info("Prebuild detected (node_modules already exists)") + switch { + case s.UseYarn: + if err := s.Yarn.Rebuild(s.Stager.BuildDir(), s.Stager.CacheDir()); err != nil { + return err; + } + default: + if err := s.NPM.Rebuild(s.Stager.BuildDir()); err != nil { + return err + } + } + } + switch { case s.UseYarn: if err := s.Yarn.Build(s.Stager.BuildDir(), s.Stager.CacheDir()); err != nil { return err } - case s.IsVendored: - s.Log.Info("Prebuild detected (node_modules already exists)") - if err := s.NPM.Rebuild(s.Stager.BuildDir()); err != nil { - return err - } - default: if err := s.NPM.Build(s.Stager.BuildDir(), s.Stager.CacheDir()); err != nil { return err diff --git a/src/nodejs/yarn/yarn.go b/src/nodejs/yarn/yarn.go index 3646294ee..eb2c8d6f5 100644 --- a/src/nodejs/yarn/yarn.go +++ b/src/nodejs/yarn/yarn.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "path/filepath" + "strings" "github.com/cloudfoundry/libbuildpack" ) @@ -22,6 +23,80 @@ type Yarn struct { func (y *Yarn) Build(buildDir, cacheDir string) error { y.Log.Info("Installing node modules (yarn.lock)") + err := y.doBuild(buildDir, cacheDir); + + if err != nil { + return err + } + + return nil; +} + +func (y *Yarn) Rebuild(buildDir, cacheDir string) error { + y.Log.Info("Rebuilding native dependencies") + + err := y.doBuild(buildDir, cacheDir); + + if err != nil { + return err + } + + return nil; +} + +func (y *Yarn) doBuild(buildDir, cacheDir string) error { + yarnVersion, err := y.getYarnVersion(buildDir) + if err != nil { + return err + } + + if strings.HasPrefix(yarnVersion, "1") { + return y.doBuildClassic(buildDir, cacheDir) + } + + return y.doBuildBerry(buildDir) +} + +func (y *Yarn) getYarnVersion(buildDir string) (string, error) { + cmd := exec.Command("yarn", "--version") + cmd.Dir = buildDir + cmd.Stdout = y.Log.Output() + cmd.Stderr = y.Log.Output() + cmd.Env = append(os.Environ(), "npm_config_nodedir="+os.Getenv("NODE_HOME")) + + versionOutput, err := cmd.Output() + if err != nil { + return "", err + } + yarnVersion := strings.TrimSpace(string(versionOutput)) + return yarnVersion, nil +} + +func (y *Yarn) isYarnLocalCacheEnabled(buildDir string) (bool, error) { + cmd := exec.Command("yarn", "config", "get", "enableGlobalCache") + cmd.Dir = buildDir + cmd.Stdout = y.Log.Output() + cmd.Stderr = y.Log.Output() + cmd.Env = append(os.Environ(), "npm_config_nodedir="+os.Getenv("NODE_HOME")) + + cacheStrategyOutput, err := cmd.Output() + if err != nil { + return false, err + } + + yarnCacheStrategy := strings.TrimSpace(string(cacheStrategyOutput)) + + if yarnCacheStrategy == "false" { + return true, nil + } + + return false, nil +} + +func (y *Yarn) doBuildClassic(buildDir, cacheDir string) error { + // Start by informing users that Yarn v1 is deprecated and they should upgrade to Yarn v4 (Berry) + y.Log.Protip("Yarn v1 is deprecated and has been replaced with Yarn Berry by the Yarn organisation. Please upgrade to Yarn v4 (Berry) to avoid future deprecation warnings.", "https://yarnpkg.com/migration/guide") + offline, err := libbuildpack.FileExists(filepath.Join(buildDir, "npm-packages-offline-cache")) if err != nil { return err @@ -41,7 +116,7 @@ func (y *Yarn) Build(buildDir, cacheDir string) error { yarnConfig["yarn-offline-mirror-pruning"] = "false" } else { y.Log.Info("Running yarn in online mode") - y.Log.Info("To run yarn in offline mode, see: https://yarnpkg.com/blog/2016/11/24/offline-mirror") + y.Log.Info("To run yarn in offline mode, see: https://classic.yarnpkg.com/blog/2016/11/24/offline-mirror/") yarnConfig["yarn-offline-mirror"] = filepath.Join(cacheDir, "npm-packages-offline-cache") yarnConfig["yarn-offline-mirror-pruning"] = "true" @@ -73,3 +148,32 @@ func (y *Yarn) Build(buildDir, cacheDir string) error { return nil } + +func (y *Yarn) doBuildBerry(buildDir string) error { + usesLocalCache, err := y.isYarnLocalCacheEnabled(buildDir) + if err != nil { + return err + } + + installArgs := []string{"install", "--immutable"} + + if usesLocalCache { + installArgs = append(installArgs, "--immutable-cache") + } + + cmd := exec.Command("yarn", installArgs...) + cmd.Dir = buildDir + cmd.Stdout = y.Log.Output() + cmd.Stderr = y.Log.Output() + cmd.Env = append(os.Environ(), "npm_config_nodedir="+os.Getenv("NODE_HOME")) + if err := y.Command.Run(cmd); err != nil { + return err + } + + err = os.RemoveAll(filepath.Join(buildDir, ".yarn/cache")) + if err != nil { + panic(err) + } + + return nil +}