diff --git a/build.go b/build.go index dcd86ff..d9882bf 100644 --- a/build.go +++ b/build.go @@ -3,11 +3,12 @@ package nodestart import ( "os" + "github.com/paketo-buildpacks/libreload-packit" "github.com/paketo-buildpacks/packit/v2" "github.com/paketo-buildpacks/packit/v2/scribe" ) -func Build(applicationFinder ApplicationFinder, logger scribe.Emitter) packit.BuildFunc { +func Build(applicationFinder ApplicationFinder, logger scribe.Emitter, reloader Reloader) packit.BuildFunc { return func(context packit.BuildContext) (packit.BuildResult, error) { logger.Title("%s %s", context.BuildpackInfo.Name, context.BuildpackInfo.Version) @@ -16,46 +17,26 @@ func Build(applicationFinder ApplicationFinder, logger scribe.Emitter) packit.Bu return packit.BuildResult{}, err } - command := "node" - args := []string{file} - - processes := []packit.Process{ - { - Type: "web", - Command: command, - Args: args, - Default: true, - Direct: true, - }, + originalProcess := packit.Process{ + Type: "web", + Command: "node", + Args: []string{file}, + Default: true, + Direct: true, } - shouldReload, err := checkLiveReloadEnabled() - if err != nil { + var processes []packit.Process + if shouldEnableReload, err := reloader.ShouldEnableLiveReload(); err != nil { return packit.BuildResult{}, err - } - - if shouldReload { - processes = []packit.Process{ - { - Type: "web", - Command: "watchexec", - Args: append([]string{ - "--restart", - "--watch", context.WorkingDir, - "--shell", "none", - "--", - command, - }, args...), - Direct: true, - Default: true, - }, - { - Type: "no-reload", - Command: command, - Args: args, - Direct: true, - }, - } + } else if shouldEnableReload { + nonReloadableProcess, reloadableProcess := reloader.TransformReloadableProcesses(originalProcess, libreload.ReloadableProcessSpec{ + WatchPaths: []string{context.WorkingDir}, + }) + reloadableProcess.Type = "web" + nonReloadableProcess.Type = "no-reload" + processes = append(processes, reloadableProcess, nonReloadableProcess) + } else { + processes = append(processes, originalProcess) } logger.LaunchProcesses(processes) diff --git a/build_test.go b/build_test.go index 0a0e805..f5fafc4 100644 --- a/build_test.go +++ b/build_test.go @@ -3,9 +3,9 @@ package nodestart_test import ( "bytes" "errors" - "fmt" "testing" + "github.com/paketo-buildpacks/libreload-packit" nodestart "github.com/paketo-buildpacks/node-start" "github.com/paketo-buildpacks/node-start/fakes" "github.com/paketo-buildpacks/packit/v2" @@ -25,6 +25,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { buffer *bytes.Buffer applicationFinder *fakes.ApplicationFinder + reloader *fakes.Reloader buildContext packit.BuildContext build packit.BuildFunc @@ -38,6 +39,8 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { applicationFinder = &fakes.ApplicationFinder{} applicationFinder.FindCall.Returns.String = "server.js" + reloader = &fakes.Reloader{} + buffer = bytes.NewBuffer(nil) logger := scribe.NewEmitter(buffer) @@ -54,7 +57,7 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { }, Layers: packit.Layers{Path: layersDir}, } - build = nodestart.Build(applicationFinder, logger) + build = nodestart.Build(applicationFinder, logger, reloader) }) it("returns a result that provides a node start command", func() { @@ -86,9 +89,17 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(buffer.String()).To(ContainSubstring("node server.js")) }) - context("when BP_LIVE_RELOAD_ENABLED=true in the build environment", func() { + context("when live reload is enabled", func() { it.Before(func() { - t.Setenv("BP_LIVE_RELOAD_ENABLED", "true") + reloader.ShouldEnableLiveReloadCall.Returns.Bool = true + reloader.TransformReloadableProcessesCall.Returns.Reloadable = packit.Process{ + Type: "Reloadable", + Command: "Reloadable-Command", + } + reloader.TransformReloadableProcessesCall.Returns.NonReloadable = packit.Process{ + Type: "Nonreloadable", + Command: "Nonreloadable-Command", + } }) it("adds a reloadable start command and makes it the default", func() { @@ -98,29 +109,28 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(result.Launch.Processes).To(Equal([]packit.Process{ { Type: "web", - Command: "watchexec", - Args: []string{ - "--restart", - "--watch", workingDir, - "--shell", "none", - "--", - "node", "server.js", - }, - Default: true, - Direct: true, + Command: "Reloadable-Command", }, { Type: "no-reload", - Command: "node", - Args: []string{"server.js"}, - Direct: true, + Command: "Nonreloadable-Command", }, })) + Expect(reloader.TransformReloadableProcessesCall.Receives.OriginalProcess).To(Equal(packit.Process{ + Type: "web", + Command: "node", + Args: []string{"server.js"}, + Default: true, + Direct: true, + })) + + Expect(reloader.TransformReloadableProcessesCall.Receives.Spec).To(Equal(libreload.ReloadableProcessSpec{ + WatchPaths: []string{workingDir}, + })) + Expect(buffer.String()).To(ContainSubstring("Some Buildpack some-version")) Expect(buffer.String()).To(ContainSubstring("Assigning launch processes")) - Expect(buffer.String()).To(ContainSubstring(fmt.Sprintf(`watchexec --restart --watch %s --shell none -- node server.js`, workingDir))) - Expect(buffer.String()).To(ContainSubstring("node server.js")) }) }) @@ -135,14 +145,15 @@ func testBuild(t *testing.T, context spec.G, it spec.S) { Expect(err).To(MatchError("failed to find application")) }) }) - context("when BP_LIVE_RELOAD_ENABLED is set to an invalid value", func() { + + context("when the reloader returns an error", func() { it.Before(func() { - t.Setenv("BP_LIVE_RELOAD_ENABLED", "not-a-bool") + reloader.ShouldEnableLiveReloadCall.Returns.Error = errors.New("reloader error") }) it("returns an error", func() { _, err := build(buildContext) - Expect(err).To(MatchError(ContainSubstring("failed to parse BP_LIVE_RELOAD_ENABLED value not-a-bool"))) + Expect(err).To(MatchError("reloader error")) }) }) }) diff --git a/detect.go b/detect.go index f0818f8..6eb3c42 100644 --- a/detect.go +++ b/detect.go @@ -1,11 +1,10 @@ package nodestart import ( - "fmt" "os" "path/filepath" - "strconv" + "github.com/paketo-buildpacks/libreload-packit" "github.com/paketo-buildpacks/packit/v2" "github.com/paketo-buildpacks/packit/v2/fs" ) @@ -15,7 +14,11 @@ type ApplicationFinder interface { Find(workingDir, launchpoint, projectPath string) (string, error) } -func Detect(applicationFinder ApplicationFinder) packit.DetectFunc { +type Reloader libreload.Reloader + +//go:generate faux --interface Reloader --output fakes/reloader.go + +func Detect(applicationFinder ApplicationFinder, reloader Reloader) packit.DetectFunc { return func(context packit.DetectContext) (packit.DetectResult, error) { _, err := applicationFinder.Find(context.WorkingDir, os.Getenv("BP_LAUNCHPOINT"), os.Getenv("BP_NODE_PROJECT_PATH")) if err != nil { @@ -30,7 +33,7 @@ func Detect(applicationFinder ApplicationFinder) packit.DetectFunc { requirements = append(requirements, newLaunchRequirement("node_modules")) } - if shouldReload, err := checkLiveReloadEnabled(); err != nil { + if shouldReload, err := reloader.ShouldEnableLiveReload(); err != nil { return packit.DetectResult{}, err } else if shouldReload { requirements = append(requirements, newLaunchRequirement("watchexec")) @@ -44,17 +47,6 @@ func Detect(applicationFinder ApplicationFinder) packit.DetectFunc { } } -func checkLiveReloadEnabled() (bool, error) { - if reload, ok := os.LookupEnv("BP_LIVE_RELOAD_ENABLED"); ok { - shouldEnableReload, err := strconv.ParseBool(reload) - if err != nil { - return false, fmt.Errorf("failed to parse BP_LIVE_RELOAD_ENABLED value %s: %w", reload, err) - } - return shouldEnableReload, nil - } - return false, nil -} - func newLaunchRequirement(name string) packit.BuildPlanRequirement { return packit.BuildPlanRequirement{ Name: name, diff --git a/detect_test.go b/detect_test.go index 771150b..8bf0ae1 100644 --- a/detect_test.go +++ b/detect_test.go @@ -19,6 +19,7 @@ func testDetect(t *testing.T, context spec.G, it spec.S) { Expect = NewWithT(t).Expect applicationFinder *fakes.ApplicationFinder + reloader *fakes.Reloader detect packit.DetectFunc workingDir string @@ -30,7 +31,9 @@ func testDetect(t *testing.T, context spec.G, it spec.S) { applicationFinder = &fakes.ApplicationFinder{} applicationFinder.FindCall.Returns.String = "./src/server.js" - detect = nodestart.Detect(applicationFinder) + reloader = &fakes.Reloader{} + + detect = nodestart.Detect(applicationFinder, reloader) }) context("when an application is detected in the working dir", func() { @@ -62,9 +65,9 @@ func testDetect(t *testing.T, context spec.G, it spec.S) { Expect(applicationFinder.FindCall.Receives.ProjectPath).To(Equal("./src")) }) - context("when BP_LIVE_RELOAD_ENABLED=true", func() { + context("when live reload is enabled", func() { it.Before(func() { - t.Setenv("BP_LIVE_RELOAD_ENABLED", "true") + reloader.ShouldEnableLiveReloadCall.Returns.Bool = true }) it("requires watchexec at launch time", func() { @@ -150,16 +153,16 @@ func testDetect(t *testing.T, context spec.G, it spec.S) { }) }) - context("when BP_LIVE_RELOAD_ENABLED is set to an invalid value", func() { + context("when the reloader returns an error", func() { it.Before(func() { - t.Setenv("BP_LIVE_RELOAD_ENABLED", "not-a-bool") + reloader.ShouldEnableLiveReloadCall.Returns.Error = errors.New("reloader error") }) it("returns an error", func() { _, err := detect(packit.DetectContext{ WorkingDir: workingDir, }) - Expect(err).To(MatchError(ContainSubstring("failed to parse BP_LIVE_RELOAD_ENABLED value not-a-bool"))) + Expect(err).To(MatchError("reloader error")) }) }, spec.Sequential()) }) diff --git a/fakes/reloader.go b/fakes/reloader.go new file mode 100644 index 0000000..ca179da --- /dev/null +++ b/fakes/reloader.go @@ -0,0 +1,54 @@ +package fakes + +import ( + "sync" + + "github.com/paketo-buildpacks/libreload-packit" + "github.com/paketo-buildpacks/packit/v2" +) + +type Reloader struct { + ShouldEnableLiveReloadCall struct { + mutex sync.Mutex + CallCount int + Returns struct { + Bool bool + Error error + } + Stub func() (bool, error) + } + TransformReloadableProcessesCall struct { + mutex sync.Mutex + CallCount int + Receives struct { + OriginalProcess packit.Process + Spec libreload.ReloadableProcessSpec + } + Returns struct { + NonReloadable packit.Process + Reloadable packit.Process + } + Stub func(packit.Process, libreload.ReloadableProcessSpec) (packit.Process, packit.Process) + } +} + +func (f *Reloader) ShouldEnableLiveReload() (bool, error) { + f.ShouldEnableLiveReloadCall.mutex.Lock() + defer f.ShouldEnableLiveReloadCall.mutex.Unlock() + f.ShouldEnableLiveReloadCall.CallCount++ + if f.ShouldEnableLiveReloadCall.Stub != nil { + return f.ShouldEnableLiveReloadCall.Stub() + } + return f.ShouldEnableLiveReloadCall.Returns.Bool, f.ShouldEnableLiveReloadCall.Returns.Error +} +func (f *Reloader) TransformReloadableProcesses(param1 packit.Process, param2 libreload.ReloadableProcessSpec) (packit.Process, packit.Process) { + f.TransformReloadableProcessesCall.mutex.Lock() + defer f.TransformReloadableProcessesCall.mutex.Unlock() + f.TransformReloadableProcessesCall.CallCount++ + f.TransformReloadableProcessesCall.Receives.OriginalProcess = param1 + f.TransformReloadableProcessesCall.Receives.Spec = param2 + if f.TransformReloadableProcessesCall.Stub != nil { + return f.TransformReloadableProcessesCall.Stub(param1, param2) + } + return f.TransformReloadableProcessesCall.Returns.NonReloadable, f.TransformReloadableProcessesCall.Returns.Reloadable +} diff --git a/go.mod b/go.mod index 613d1f5..5773c32 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/BurntSushi/toml v1.2.0 github.com/onsi/gomega v1.20.2 + github.com/paketo-buildpacks/libreload-packit v0.0.1 github.com/paketo-buildpacks/occam v0.13.2 github.com/paketo-buildpacks/packit/v2 v2.5.1 github.com/sclevine/spec v1.4.0 @@ -37,7 +38,7 @@ require ( github.com/oklog/ulid v1.3.1 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.3-0.20220114050600-8b9d41f48198 // indirect - github.com/opencontainers/runc v1.1.3 // indirect + github.com/opencontainers/runc v1.1.4 // indirect github.com/pelletier/go-toml v1.9.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sirupsen/logrus v1.9.0 // indirect diff --git a/go.sum b/go.sum index c7e29c5..d457161 100644 --- a/go.sum +++ b/go.sum @@ -1748,8 +1748,9 @@ github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84 github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= github.com/opencontainers/runc v1.1.2/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc= -github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w= github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= +github.com/opencontainers/runc v1.1.4 h1:nRCz/8sKg6K6jgYAFLDlXzPeITBZJyX28DBVhWD+5dg= +github.com/opencontainers/runc v1.1.4/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= @@ -1777,6 +1778,8 @@ github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6 github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/paketo-buildpacks/libreload-packit v0.0.1 h1:K1HhNAqBSzRpefwGOcvdchZwyeNTgNJL9SC7V4paYt8= +github.com/paketo-buildpacks/libreload-packit v0.0.1/go.mod h1:ZWE3U94Z18yJk8Pc1mP852l9phQsrsZ+An2U98g0rWw= github.com/paketo-buildpacks/occam v0.13.2 h1:4hgDCVx4iHSfk3qDpaFzja94TOJ0tOJ6hOLsrHuUAhw= github.com/paketo-buildpacks/occam v0.13.2/go.mod h1:/kuPXK3OXdTmVbZrd6OWmC4/dwrmus6l+MTwhcDbD5A= github.com/paketo-buildpacks/packit/v2 v2.1.0/go.mod h1:Ud1X7f9O6e4eGPZlY3pj0jnSa9oDK2L/ghvjmarTLSA= diff --git a/run/main.go b/run/main.go index 5c82af3..3684eee 100644 --- a/run/main.go +++ b/run/main.go @@ -3,6 +3,7 @@ package main import ( "os" + "github.com/paketo-buildpacks/libreload-packit/watchexec" nodestart "github.com/paketo-buildpacks/node-start" "github.com/paketo-buildpacks/packit/v2" "github.com/paketo-buildpacks/packit/v2/scribe" @@ -11,12 +12,15 @@ import ( func main() { nodeApplicationFinder := nodestart.NewNodeApplicationFinder() logger := scribe.NewEmitter(os.Stdout).WithLevel(os.Getenv("BP_LOG_LEVEL")) - + + reloader := watchexec.NewWatchexecReloader() + packit.Run( - nodestart.Detect(nodeApplicationFinder), + nodestart.Detect(nodeApplicationFinder, reloader), nodestart.Build( nodeApplicationFinder, logger, + reloader, ), ) }