Skip to content

Commit

Permalink
Use libreload-packit to handle live reload
Browse files Browse the repository at this point in the history
  • Loading branch information
joshuatcasey committed Sep 19, 2022
1 parent 43f870f commit 1854478
Show file tree
Hide file tree
Showing 7 changed files with 77 additions and 83 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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
8 changes: 6 additions & 2 deletions run/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
),
)
}

0 comments on commit 1854478

Please sign in to comment.