Skip to content

Commit

Permalink
Use libreload-packit to handle live reload (#270)
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuatcasey authored Sep 21, 2022
1 parent 7a7047e commit b83a337
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 85 deletions.
57 changes: 19 additions & 38 deletions build.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -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)
Expand Down
55 changes: 33 additions & 22 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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
Expand All @@ -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)

Expand All @@ -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() {
Expand Down Expand Up @@ -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() {
Expand All @@ -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"))
})
})

Expand All @@ -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"))
})
})
})
Expand Down
22 changes: 7 additions & 15 deletions detect.go
Original file line number Diff line number Diff line change
@@ -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"
)
Expand All @@ -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 {
Expand All @@ -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"))
Expand All @@ -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,
Expand Down
15 changes: 9 additions & 6 deletions detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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())
})
Expand Down
54 changes: 54 additions & 0 deletions fakes/reloader.go
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down Expand Up @@ -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=
Expand Down
Loading

0 comments on commit b83a337

Please sign in to comment.