From 1854478aa51f6582de2b78b358b363a2a303fe92 Mon Sep 17 00:00:00 2001 From: Joshua Casey Date: Sun, 18 Sep 2022 23:47:06 -0500 Subject: [PATCH] Use libreload-packit to handle live reload --- build.go | 57 +++++++++++++++++--------------------------------- build_test.go | 55 +++++++++++++++++++++++++++++------------------- detect.go | 22 +++++++------------ detect_test.go | 15 +++++++------ go.mod | 1 + go.sum | 2 ++ run/main.go | 8 +++++-- 7 files changed, 77 insertions(+), 83 deletions(-) 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/go.mod b/go.mod index b2b24a7..969f0dd 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( 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.4 // indirect + github.com/paketo-buildpacks/libreload-packit v0.0.1 // 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 29ddc48..2a7f9ab 100644 --- a/go.sum +++ b/go.sum @@ -1731,6 +1731,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.1 h1:qqkzCsxKkaeC64gC3555+qWTvqSS5kDgLMY9lct2rSQ= github.com/paketo-buildpacks/occam v0.13.1/go.mod h1:Mm7Do1u4au2qIUXd1XVeBQRFdLXI/yCdMns5J9Nph34= 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, ), ) }