diff --git a/src/nodejs/supply/mocks_test.go b/src/nodejs/supply/mocks_test.go index dd78457ec..3aa86e885 100644 --- a/src/nodejs/supply/mocks_test.go +++ b/src/nodejs/supply/mocks_test.go @@ -1,5 +1,5 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: supply.go +// Source: src/nodejs/supply/supply.go // Package supply_test is a generated GoMock package. package supply_test @@ -232,17 +232,17 @@ func (m *MockYarn) EXPECT() *MockYarnMockRecorder { } // Build mocks base method. -func (m *MockYarn) Build(arg0, arg1 string) error { +func (m *MockYarn) Build(buildDir, cacheDir string, isBerry bool) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Build", arg0, arg1) + ret := m.ctrl.Call(m, "Build", buildDir, cacheDir, isBerry) ret0, _ := ret[0].(error) return ret0 } // Build indicates an expected call of Build. -func (mr *MockYarnMockRecorder) Build(arg0, arg1 interface{}) *gomock.Call { +func (mr *MockYarnMockRecorder) Build(buildDir, cacheDir, isBerry interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockYarn)(nil).Build), arg0, arg1) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockYarn)(nil).Build), buildDir, cacheDir, isBerry) } // MockStager is a mock of Stager interface. diff --git a/src/nodejs/supply/supply.go b/src/nodejs/supply/supply.go index 5d687091e..46385f550 100644 --- a/src/nodejs/supply/supply.go +++ b/src/nodejs/supply/supply.go @@ -45,7 +45,7 @@ type NPM interface { } type Yarn interface { - Build(string, string) error + Build(buildDir, cacheDir string, isBerry bool) error } type Stager interface { @@ -77,6 +77,7 @@ type Supplier struct { PostBuild string UseYarn bool UsesYarnWorkspaces bool + UsesYarnBerry bool IsVendored bool Yarn Yarn NPM NPM @@ -124,9 +125,16 @@ func Run(s *Supplier) error { return err } - if err := s.InstallYarn(); err != nil { - s.Log.Error("Unable to install yarn: %s", err.Error()) - return err + if s.UsesYarnBerry { + if err := s.InstallYarnBerry(); err != nil { + s.Log.Error("Unable to install yarn (berry): %s", err.Error()) + return err + } + } else { + if err := s.InstallYarn(); err != nil { + s.Log.Error("Unable to install yarn: %s", err.Error()) + return err + } } if err := s.CreateDefaultEnv(); err != nil { @@ -317,7 +325,7 @@ func (s *Supplier) BuildDependencies() error { switch { case s.UseYarn: - if err := s.Yarn.Build(s.Stager.BuildDir(), s.Stager.CacheDir()); err != nil { + if err := s.Yarn.Build(s.Stager.BuildDir(), s.Stager.CacheDir(), s.UsesYarnBerry); err != nil { return err } @@ -400,6 +408,16 @@ func (s *Supplier) ReadPackageJSON() error { return err } + if s.UseYarn { + // .yarnrc.yml is the marker file Yarn Berry (2.x/3.x/4.x) itself uses to + // identify a project as using the "modern" (non-Classic) CLI/protocol - + // Yarn Classic (1.x) projects never have this file. See + // https://yarnpkg.com/configuration/yarnrc. + if s.UsesYarnBerry, err = libbuildpack.FileExists(filepath.Join(s.Stager.BuildDir(), ".yarnrc.yml")); err != nil { + return err + } + } + if s.IsVendored, err = libbuildpack.FileExists(filepath.Join(s.Stager.BuildDir(), "node_modules")); err != nil { return err } @@ -799,6 +817,57 @@ func (s *Supplier) InstallYarn() error { return nil } +// InstallYarnBerry installs the "yarn-berry" dependency (Yarn 2.x/3.x/4.x) for +// projects whose .yarnrc.yml marks them as using the modern Yarn CLI. +// +// Unlike Yarn Classic, Yarn Berry is not distributed as a self-contained +// installable tarball with a bin/ directory - it ships as a single bundled +// CLI script (see https://repo.yarnpkg.com). InstallOnlyVersion therefore +// just copies that file as-is into the install dir (libbuildpack falls back +// to a plain copy for any dependency URI it doesn't recognize as an +// archive), so a small wrapper script is created here to expose it on PATH +// as a normal "yarn" executable via `node