diff --git a/src/nodejs/supply/mocks_test.go b/src/nodejs/supply/mocks_test.go index 3aa86e885..27d5509d7 100644 --- a/src/nodejs/supply/mocks_test.go +++ b/src/nodejs/supply/mocks_test.go @@ -245,6 +245,20 @@ func (mr *MockYarnMockRecorder) Build(buildDir, cacheDir, isBerry interface{}) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Build", reflect.TypeOf((*MockYarn)(nil).Build), buildDir, cacheDir, isBerry) } +// Rebuild mocks base method. +func (m *MockYarn) Rebuild(buildDir string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Rebuild", buildDir) + ret0, _ := ret[0].(error) + return ret0 +} + +// Rebuild indicates an expected call of Rebuild. +func (mr *MockYarnMockRecorder) Rebuild(buildDir interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Rebuild", reflect.TypeOf((*MockYarn)(nil).Rebuild), buildDir) +} + // 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 d908a0e4b..d29e03312 100644 --- a/src/nodejs/supply/supply.go +++ b/src/nodejs/supply/supply.go @@ -46,6 +46,7 @@ type NPM interface { type Yarn interface { Build(buildDir, cacheDir string, isBerry bool) error + Rebuild(buildDir string) error } type PNPM interface { @@ -340,6 +341,12 @@ func (s *Supplier) BuildDependencies() error { } switch { + case s.UseYarn && s.IsVendored: + s.Log.Info("Prebuild detected (node_modules already exists)") + if err := s.Yarn.Rebuild(s.Stager.BuildDir()); err != nil { + return err + } + case s.UseYarn: if err := s.Yarn.Build(s.Stager.BuildDir(), s.Stager.CacheDir(), s.UsesYarnBerry); err != nil { return err diff --git a/src/nodejs/supply/supply_test.go b/src/nodejs/supply/supply_test.go index 02b59aa81..b19688814 100644 --- a/src/nodejs/supply/supply_test.go +++ b/src/nodejs/supply/supply_test.go @@ -1201,6 +1201,35 @@ var _ = Describe("Supply", func() { }) }) + Context("using yarn with vendored node_modules", func() { + BeforeEach(func() { + supplier.UseYarn = true + supplier.IsVendored = true + }) + + It("runs yarn rebuild instead of yarn build", func() { + mockYarn.EXPECT().Rebuild(buildDir).Return(nil) + Expect(supplier.BuildDependencies()).To(Succeed()) + Expect(buffer.String()).To(ContainSubstring("Prebuild detected (node_modules already exists)")) + }) + + It("runs prebuild script when specified", func() { + supplier.PreBuild = "prescriptive" + mockYarn.EXPECT().Rebuild(buildDir).Return(nil) + mockCommand.EXPECT().Execute(buildDir, gomock.Any(), gomock.Any(), "yarn", "run", "heroku-prebuild") + Expect(supplier.BuildDependencies()).To(Succeed()) + Expect(buffer.String()).To(ContainSubstring("Running heroku-prebuild (yarn)")) + }) + + It("runs postbuild script when specified", func() { + supplier.PostBuild = "descriptive" + mockYarn.EXPECT().Rebuild(buildDir).Return(nil) + mockCommand.EXPECT().Execute(buildDir, gomock.Any(), gomock.Any(), "yarn", "run", "heroku-postbuild") + Expect(supplier.BuildDependencies()).To(Succeed()) + Expect(buffer.String()).To(ContainSubstring("Running heroku-postbuild (yarn)")) + }) + }) + Describe("using npm", func() { BeforeEach(func() { supplier.UseYarn = false diff --git a/src/nodejs/yarn/yarn.go b/src/nodejs/yarn/yarn.go index 713b82a6a..83e4b2d5f 100644 --- a/src/nodejs/yarn/yarn.go +++ b/src/nodejs/yarn/yarn.go @@ -99,3 +99,13 @@ func (y *Yarn) buildClassic(buildDir, cacheDir string) error { return nil } + +func (y *Yarn) Rebuild(buildDir string) error { + y.Log.Info("Rebuilding any native modules (yarn)") + cmd := exec.Command("yarn", "rebuild") + 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")) + return y.Command.Run(cmd) +} diff --git a/src/nodejs/yarn/yarn_test.go b/src/nodejs/yarn/yarn_test.go index d98b78beb..107d3a05f 100644 --- a/src/nodejs/yarn/yarn_test.go +++ b/src/nodejs/yarn/yarn_test.go @@ -144,4 +144,34 @@ var _ = Describe("Yarn", func() { }) }) }) + + Describe("Rebuild", func() { + var oldNodeHome string + + BeforeEach(func() { + oldNodeHome = os.Getenv("NODE_HOME") + Expect(os.Setenv("NODE_HOME", "test_node_home")).To(Succeed()) + }) + + AfterEach(func() { + Expect(os.Setenv("NODE_HOME", oldNodeHome)).To(Succeed()) + }) + + It("runs yarn rebuild with NODE_HOME set", func() { + mockCommand.EXPECT().Run(gomock.Any()).Do(func(cmd *exec.Cmd) { + Expect(cmd.Args).To(Equal([]string{"yarn", "rebuild"})) + Expect(cmd.Dir).To(Equal(buildDir)) + Expect(cmd.Env).To(ContainElement("npm_config_nodedir=test_node_home")) + }) + + Expect(y.Rebuild(buildDir)).To(Succeed()) + Expect(buffer.String()).To(ContainSubstring("Rebuilding any native modules (yarn)")) + }) + + It("propagates command errors", func() { + mockCommand.EXPECT().Run(gomock.Any()).Return(os.ErrNotExist) + + Expect(y.Rebuild(buildDir)).To(Equal(os.ErrNotExist)) + }) + }) })