diff --git a/.gitignore b/.gitignore index 17b956ba..fe3c001b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,6 @@ Dockerfile.initium *.init_test* initium_onmain.yaml initium_onbranch.yaml -dist/ +/dist node_modules diff --git a/README.md b/README.md index e7ce75f8..1180a1ab 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Following we have a matrix related to which application runtime our CLI is curre |----------------------|:------------------:| | [Nodejs](https://github.com/nearform/initium-cli/blob/main/assets/docker/Dockerfile.node.tmpl) | :white_check_mark: | | [GoLang](https://github.com/nearform/initium-cli/blob/main/assets/docker/Dockerfile.go.tmpl) | :white_check_mark: | +| [JS Frontend](https://github.com/nearform/initium-cli/blob/main/assets/docker/Dockerfile.frontend-js.tmpl) | :white_check_mark: | | Python | Coming Soon | | More will be added...| | diff --git a/assets/docker/Dockerfile.frontend-js.tmpl b/assets/docker/Dockerfile.frontend-js.tmpl new file mode 100644 index 00000000..87f06a33 --- /dev/null +++ b/assets/docker/Dockerfile.frontend-js.tmpl @@ -0,0 +1,24 @@ +FROM node:{{ or .RuntimeVersion .DefaultRuntimeVersion }} AS build-env + +ENV CI=true + +WORKDIR /app + +COPY package*.json tsconfig*.json ./ + +RUN {{ .NodeInstallCommand }} + +COPY . . + +RUN npm run build --if-present +RUN npm test + +FROM node:{{ or .RuntimeVersion .DefaultRuntimeVersion }} + +COPY --from=build-env /app /app + +WORKDIR /app + +USER node + +ENTRYPOINT npx http-server ./build diff --git a/assets/docker/Dockerfile.go.tmpl b/assets/docker/Dockerfile.go.tmpl index 115fd187..91f9b7e7 100644 --- a/assets/docker/Dockerfile.go.tmpl +++ b/assets/docker/Dockerfile.go.tmpl @@ -12,4 +12,4 @@ RUN CGO_ENABLED=0 go build -o /go/bin/app FROM gcr.io/distroless/static-debian11 COPY --from=build /go/bin/app / -ENTRYPOINT ["/app"] \ No newline at end of file +ENTRYPOINT ["/app"] diff --git a/assets/docker/Dockerfile.node.tmpl b/assets/docker/Dockerfile.node.tmpl index c871a9ca..00199f50 100644 --- a/assets/docker/Dockerfile.node.tmpl +++ b/assets/docker/Dockerfile.node.tmpl @@ -15,4 +15,4 @@ FROM gcr.io/distroless/nodejs20-debian11 COPY --from=build-env /app /app WORKDIR /app USER nonroot -CMD ["index.js"] \ No newline at end of file +CMD ["index.js"] diff --git a/example/frontend-js/build/index.html b/example/frontend-js/build/index.html new file mode 100644 index 00000000..ee7a0cda --- /dev/null +++ b/example/frontend-js/build/index.html @@ -0,0 +1,14 @@ + + + + + + + Initium Frontend Sample + + + + Welcome to Initium! + + + diff --git a/example/frontend-js/package.json b/example/frontend-js/package.json new file mode 100644 index 00000000..5b86e0fc --- /dev/null +++ b/example/frontend-js/package.json @@ -0,0 +1,38 @@ +{ + "name": "frontend-js", + "version": "0.1.0", + "private": true, + "dependencies": { + "@testing-library/jest-dom": "^5.17.0", + "@testing-library/react": "^13.4.0", + "@testing-library/user-event": "^13.5.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-scripts": "5.0.1", + "web-vitals": "^2.1.4" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": [ + "react-app", + "react-app/jest" + ] + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } +} diff --git a/example/.env.sample b/example/node/.env.sample similarity index 100% rename from example/.env.sample rename to example/node/.env.sample diff --git a/example/index.js b/example/node/index.js similarity index 100% rename from example/index.js rename to example/node/index.js diff --git a/example/package.json b/example/node/package.json similarity index 100% rename from example/package.json rename to example/node/package.json diff --git a/src/cli/template_test.go b/src/cli/template_test.go index 8b0a4311..7400856e 100644 --- a/src/cli/template_test.go +++ b/src/cli/template_test.go @@ -29,6 +29,7 @@ FROM gcr.io/distroless/static-debian11 COPY --from=build /go/bin/app / ENTRYPOINT ["/app"] + ` const expectedNodeAppDockerTemplate = `FROM node:20.2.0 AS build-env @@ -49,11 +50,40 @@ COPY --from=build-env /app /app WORKDIR /app USER nonroot CMD ["index.js"] + +` + +const expectedFrontendJsAppDockerTemplate = `FROM node:20.2.0 AS build-env + +ENV CI=true + +WORKDIR /app + +COPY package*.json tsconfig*.json ./ + +RUN npm i + +COPY . . + +RUN npm run build --if-present +RUN npm test + +FROM node:20.2.0 + +COPY --from=build-env /app /app + +WORKDIR /app + +USER node + +CMD npx http-server -p 8080 ./build + ` var projects = map[project.ProjectType]map[string]string{ - project.NodeProject: {"directory": "example", "expectedTemplate": expectedNodeAppDockerTemplate}, - project.GoProject: {"directory": ".", "expectedTemplate": expectedGoAppDockerTemplate}, + project.NodeProject: {"directory": "example/node", "expectedTemplate": expectedNodeAppDockerTemplate}, + project.GoProject: {"directory": ".", "expectedTemplate": expectedGoAppDockerTemplate}, + project.FrontendJsProject: {"directory": "example/frontend-js", "expectedTemplate": expectedFrontendJsAppDockerTemplate}, } func TestShouldRenderDockerTemplate(t *testing.T) { diff --git a/src/services/k8s/knative_test.go b/src/services/k8s/knative_test.go index 47704357..e5e95db2 100644 --- a/src/services/k8s/knative_test.go +++ b/src/services/k8s/knative_test.go @@ -72,7 +72,7 @@ func TestLoadManifestForPrivateService(t *testing.T) { Tag: "v1.1.0", } - serviceManifest, err := LoadManifest(namespace, commitSha, proj, dockerImage, path.Join(root, "example/.env.sample")) + serviceManifest, err := LoadManifest(namespace, commitSha, proj, dockerImage, path.Join(root, "example/node/.env.sample")) if err != nil { t.Fatalf(fmt.Sprintf("Error: %v", err)) @@ -93,7 +93,7 @@ func TestLoadManifestForPublicService(t *testing.T) { imagePullSecrets := []string{"secretPassword123"} proj := &project.Project{Name: "knative_test", - Directory: path.Join(root, "example"), + Directory: path.Join(root, "example/node"), Resources: os.DirFS(root), ImagePullSecrets: imagePullSecrets, IsPrivate: true, @@ -106,7 +106,7 @@ func TestLoadManifestForPublicService(t *testing.T) { Tag: "v1.1.0", } - serviceManifest, err := LoadManifest(namespace, commitSha, proj, dockerImage, path.Join(root, "example/.env.sample")) + serviceManifest, err := LoadManifest(namespace, commitSha, proj, dockerImage, path.Join(root, "example/node/.env.sample")) if err != nil { t.Fatalf(fmt.Sprintf("Error: %v", err)) diff --git a/src/services/project/project.go b/src/services/project/project.go index ce42c300..d58b3eb0 100644 --- a/src/services/project/project.go +++ b/src/services/project/project.go @@ -2,10 +2,12 @@ package project import ( "bytes" + "encoding/json" "fmt" "io/fs" "os" "path" + "strings" "text/template" "github.com/nearform/initium-cli/src/services/git" @@ -15,8 +17,9 @@ import ( type ProjectType string const ( - NodeProject ProjectType = "node" - GoProject ProjectType = "go" + NodeProject ProjectType = "node" + GoProject ProjectType = "go" + FrontendJsProject ProjectType = "frontend-js" ) type Project struct { @@ -65,6 +68,8 @@ func IsValidProjectType(projectType string) bool { return true case string(GoProject): return true + case string(FrontendJsProject): + return true default: return false } @@ -74,8 +79,28 @@ func DetectType(directory string) (ProjectType, error) { var detectedRuntimes []ProjectType var projectType ProjectType if _, err := os.Stat(path.Join(directory, "package.json")); err == nil { - detectedRuntimes = append(detectedRuntimes, NodeProject) - projectType = NodeProject + bytes, err := os.ReadFile(path.Join(directory, "package.json")) + var result map[string]any + json.Unmarshal(bytes, &result) + dependencies := result["dependencies"].(map[string]any) + + if err != nil { + fmt.Print(err) + } + + frontendDetected := false + for dependency := range dependencies { + if strings.Contains(string(dependency), "react") || strings.Contains(string(dependency), "angular") || strings.Contains(string(dependency), "vue") { + frontendDetected = true + } + } + if frontendDetected { + detectedRuntimes = append(detectedRuntimes, FrontendJsProject) + projectType = FrontendJsProject + } else { + detectedRuntimes = append(detectedRuntimes, NodeProject) + projectType = NodeProject + } } if _, err := os.Stat(path.Join(directory, "go.mod")); err == nil { detectedRuntimes = append(detectedRuntimes, GoProject) @@ -98,6 +123,9 @@ func (proj *Project) setRuntimeVersion() error { case GoProject: proj.DefaultRuntimeVersion = defaults.DefaultGoRuntimeVersion return nil + case FrontendJsProject: + proj.DefaultRuntimeVersion = defaults.DefaultFrontendJsRuntimeVersion + return nil default: return fmt.Errorf("cannot detect runtime version for project type %s", proj.Type) } diff --git a/src/services/project/project_test.go b/src/services/project/project_test.go index 22992b2d..8572579c 100644 --- a/src/services/project/project_test.go +++ b/src/services/project/project_test.go @@ -11,9 +11,10 @@ import ( ) var projects = map[ProjectType]map[string]string{ - NodeProject: {"directory": "example"}, - GoProject: {"directory": "."}, - "invalid": {"directory": "src"}, + NodeProject: {"directory": "example/node"}, + FrontendJsProject: {"directory": "example/frontend-js"}, + GoProject: {"directory": "."}, + "invalid": {"directory": "src"}, } var root = "../../../" @@ -139,7 +140,7 @@ func TestNodeInstallCommand(t *testing.T) { Resources: os.DirFS(root), } - if project_type == NodeProject { + if project_type == NodeProject || project_type == FrontendJsProject { expected := "npm i" installCommand := proj.NodeInstallCommand() if installCommand != expected { diff --git a/src/utils/defaults/defaults.go b/src/utils/defaults/defaults.go index 78c67281..07cd2dff 100644 --- a/src/utils/defaults/defaults.go +++ b/src/utils/defaults/defaults.go @@ -16,3 +16,5 @@ const DefaultNodeRuntimeVersion string = "20.2.0" // renovate: datasource=docker depName=golang const DefaultGoRuntimeVersion string = "1.20.4" + +const DefaultFrontendJsRuntimeVersion string = "20.2.0"