From cc37aae2faf383b16ef738a58d716a1e637f00aa Mon Sep 17 00:00:00 2001 From: Michael Dawson Date: Tue, 2 May 2023 11:09:27 -0400 Subject: [PATCH] move function to find applications into libnodejs Move function from to find applications from node-start into libnodejs so that it can be used across buildpacks and extensions. API has been refined from what was in node-start to make it better as a shared function. Signed-off-by: Michael Dawson --- find_node_application.go | 46 +++++++++ find_node_application_test.go | 187 ++++++++++++++++++++++++++++++++++ init_test.go | 1 + spec_constants.go | 1 + 4 files changed, 235 insertions(+) create mode 100644 find_node_application.go create mode 100644 find_node_application_test.go diff --git a/find_node_application.go b/find_node_application.go new file mode 100644 index 0000000..b0c827c --- /dev/null +++ b/find_node_application.go @@ -0,0 +1,46 @@ +package libnodejs + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "strings" +) + +func FindNodeApplication(workingDir string) (string, error) { + + projectPath, err := FindProjectPath(workingDir) + if err != nil { + return "", err + } + + launchpoint := os.Getenv(LaunchPointEnvName) + if launchpoint != "" { + if _, err := os.Stat(filepath.Join(workingDir, launchpoint)); err != nil { + if errors.Is(err, os.ErrNotExist) { + return "", fmt.Errorf("expected value derived from BP_LAUNCHPOINT [%s] to be an existing file", launchpoint) + } + + return "", err + } + + return filepath.Clean(launchpoint), nil + } + + files := []string{"server.js", "app.js", "main.js", "index.js"} + for _, file := range files { + _, err := os.Stat(filepath.Join(projectPath, file)) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + continue + } + + return "", err + } + + return filepath.Join(os.Getenv(ProjectPathEnvName), file), nil + } + + return "", fmt.Errorf("could not find app in %s: expected one of %s", filepath.Clean(projectPath), strings.Join(files, " | ")) +} diff --git a/find_node_application_test.go b/find_node_application_test.go new file mode 100644 index 0000000..4440743 --- /dev/null +++ b/find_node_application_test.go @@ -0,0 +1,187 @@ +package libnodejs_test + +import ( + "fmt" + "os" + "path/filepath" + "testing" + + "github.com/paketo-buildpacks/libnodejs" + "github.com/sclevine/spec" + + . "github.com/onsi/gomega" +) + +func testFindNodeApplication(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + + workingDir string + ) + + it.Before(func() { + workingDir = t.TempDir() + }) + + it.After(func() { + Expect(os.RemoveAll(workingDir)).To(Succeed()) + }) + + context("finds the server.js application entrypoint", func() { + it.Before(func() { + Expect(os.WriteFile(filepath.Join(workingDir, "server.js"), nil, 0600)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "app.js"), nil, 0600)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "main.js"), nil, 0600)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "index.js"), nil, 0600)).To(Succeed()) + }) + + it("finds the server.js application entrypoint successfully", func() { + file, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).NotTo(HaveOccurred()) + Expect(file).To(Equal(filepath.Join("server.js"))) + }) + }) + + context("finds the app.js application entrypoint", func() { + it.Before(func() { + Expect(os.WriteFile(filepath.Join(workingDir, "app.js"), nil, 0600)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "main.js"), nil, 0600)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "index.js"), nil, 0600)).To(Succeed()) + }) + + it("finds the app.js application entrypoint successfully", func() { + file, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).NotTo(HaveOccurred()) + Expect(file).To(Equal(filepath.Join("app.js"))) + }) + }) + + context("finds the main.js application entrypoint", func() { + it.Before(func() { + Expect(os.WriteFile(filepath.Join(workingDir, "main.js"), nil, 0600)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "index.js"), nil, 0600)).To(Succeed()) + }) + + it("finds the main.js application entrypoint successfully", func() { + file, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).NotTo(HaveOccurred()) + Expect(file).To(Equal(filepath.Join("main.js"))) + }) + }) + + context("finds the index.js application entrypoint", func() { + it.Before(func() { + Expect(os.WriteFile(filepath.Join(workingDir, "index.js"), nil, 0600)).To(Succeed()) + }) + + it("finds the index.js application entrypoint", func() { + file, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).NotTo(HaveOccurred()) + Expect(file).To(Equal(filepath.Join("index.js"))) + }) + }) + + context("when there is a launchpoint", func() { + it.Before(func() { + Expect(os.Mkdir(filepath.Join(workingDir, "src"), os.ModePerm)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "src", "launchpoint.js"), nil, 0600)).To(Succeed()) + }) + + context("when the launchpoint file exists", func() { + it("returns the highest priority file", func() { + t.Setenv("BP_LAUNCHPOINT", "./src/launchpoint.js") + file, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).NotTo(HaveOccurred()) + Expect(file).To(Equal(filepath.Join("src", "launchpoint.js"))) + }) + }) + + context("when the launchpoint file does not exist", func() { + it("returns the empty string and no error", func() { + t.Setenv("BP_LAUNCHPOINT", "./no-such-file.js") + file, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).To(MatchError(ContainSubstring("expected value derived from BP_LAUNCHPOINT [./no-such-file.js] to be an existing file"))) + Expect(file).To(Equal("")) + }) + }) + }) + + context("when there is a project path", func() { + it.Before(func() { + Expect(os.Mkdir(filepath.Join(workingDir, "frontend"), os.ModePerm)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "frontend", "server.js"), nil, 0600)).To(Succeed()) + Expect(os.WriteFile(filepath.Join(workingDir, "frontend", "app.js"), nil, 0600)).To(Succeed()) + }) + + it("returns the highest priority file", func() { + t.Setenv("BP_NODE_PROJECT_PATH", "frontend") + file, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).NotTo(HaveOccurred()) + Expect(file).To(Equal(filepath.Join("frontend", "server.js"))) + }) + }) + + context("when there is a project path but value specified does not exist", func() { + it("returns a failure", func() { + t.Setenv("BP_NODE_PROJECT_PATH", "frontend") + _, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).To(MatchError(ContainSubstring("no such file or directory"))) + }) + }) + + context("when no application can be found", func() { + it("returns that application could not be found", func() { + _, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).To(MatchError(fmt.Errorf("could not find app in %s: expected one of server.js | app.js | main.js | index.js", workingDir))) + }) + }) + + context("failure cases", func() { + context("when the launchpoint cannot be stat'd", func() { + it.Before(func() { + Expect(os.Chmod(workingDir, 0000)).To(Succeed()) + }) + + it.After(func() { + Expect(os.Chmod(workingDir, os.ModePerm)).To(Succeed()) + }) + + it("fails with helpful error", func() { + t.Setenv("BP_LAUNCHPOINT", "something.js") + _, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) + + context("when the working dir cannot be stat'd", func() { + it.Before(func() { + Expect(os.Chmod(workingDir, 0000)).To(Succeed()) + }) + + it.After(func() { + Expect(os.Chmod(workingDir, os.ModePerm)).To(Succeed()) + }) + + it("fails with helpful error", func() { + _, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) + + context("when the project path cannot be stat'd", func() { + it.Before(func() { + Expect(os.Chmod(workingDir, 0000)).To(Succeed()) + }) + + it.After(func() { + Expect(os.Chmod(workingDir, os.ModePerm)).To(Succeed()) + }) + + it("fails with helpful error", func() { + t.Setenv("BP_NODE_PROJECT_PATH", "frontend") + _, err := libnodejs.FindNodeApplication(workingDir) + Expect(err).To(MatchError(ContainSubstring("permission denied"))) + }) + }) + }) +} diff --git a/init_test.go b/init_test.go index 7011d08..56f70bc 100644 --- a/init_test.go +++ b/init_test.go @@ -11,5 +11,6 @@ func TestUnitLibnodejs(t *testing.T) { suite := spec.New("libnodejs", spec.Report(report.Terminal{}), spec.Sequential()) suite("FindProjectPath", testFindProjectPath) suite("PackageJSON", testPackageJSON) + suite("FindNodeApplication", testFindNodeApplication) suite.Run(t) } diff --git a/spec_constants.go b/spec_constants.go index 274855f..10cd400 100644 --- a/spec_constants.go +++ b/spec_constants.go @@ -1,3 +1,4 @@ package libnodejs const ProjectPathEnvName = "BP_NODE_PROJECT_PATH" +const LaunchPointEnvName = "BP_LAUNCHPOINT"