Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: make UI dev easier #1478

Merged
merged 9 commits into from
Apr 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Before starting, make sure you have the following installed:
1. Clone this repo: `git clone https://github.com/flipt-io/flipt`
1. Run `mage bootstrap` to install required development tools. See [#bootstrap](#bootstrap) below.
1. Run `mage test` to execute the test suite
1. Run `mage build` to build the binary with embedded assets.
1. Run `mage` to build the binary with embedded assets.
1. Run `mage -l` to see a full list of possible commands

## Go
Expand Down
74 changes: 1 addition & 73 deletions go.work.sum

Large diffs are not rendered by default.

140 changes: 88 additions & 52 deletions magefile.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ package main

import (
"fmt"
"html/template"
"os"
"os/exec"
"path/filepath"
"time"

"github.com/fatih/color"
"github.com/magefile/mage/mg"
"github.com/magefile/mage/sh"
)
Expand Down Expand Up @@ -47,7 +49,7 @@ func Bench() error {
func Bootstrap() error {
fmt.Println("Bootstrapping tools...")
if err := os.MkdirAll("_tools", 0755); err != nil {
return fmt.Errorf("failed to create dir: %w", err)
return fmt.Errorf("creating dir: %w", err)
}

// create module if go.mod doesnt exist
Expand All @@ -73,15 +75,16 @@ func Bootstrap() error {

// Build builds the project similar to a release build
func Build() error {
mg.Deps(Prep)
mg.Deps(Clean)
fmt.Println("Building...")

if err := build([]string{"-tags", "assets"}...); err != nil {
if err := build(buildModeProd); err != nil {
return err
}

fmt.Println("Done.")
fmt.Println("Run `./bin/flipt [--config config/local.yml]` to start Flipt")
fmt.Printf("\nRun the following to start Flipt:\n")
fmt.Printf("\n%v\n", color.CyanString(`./bin/flipt --config config/local.yml`))
return nil
}

Expand All @@ -90,24 +93,96 @@ func Dev() error {
mg.Deps(Clean)
fmt.Println("Building...")

if err := build(); err != nil {
if err := build(buildModeDev); err != nil {
return err
}

fmt.Println("Done.")
fmt.Println("Run `./bin/flipt [--config config/local.yml]` to start Flipt")
fmt.Printf("\nRun the following to start Flipt server:\n")
fmt.Printf("\n%v\n", color.CyanString(`./bin/flipt --config config/local.yml`))
fmt.Printf("\nIn another shell, run the following to start the UI in dev mode:\n")
fmt.Printf("\n%v\n", color.CyanString(`cd ui && npm run dev`))
return nil
}

func build(args ...string) error {
type buildMode uint8

const (
// buildModeDev builds the project for development, without bundling assets
buildModeDev buildMode = iota
// BuildModeProd builds the project similar to a release build
buildModeProd
)

func ui(mode buildMode) error {
// use template to determine if we should inject dev scripts
// required to proxy to vite dev server
// see: https://vitejs.dev/guide/backend-integration.html
tmplt := template.Must(template.ParseFiles("ui/index.html.tmpl"))

v := struct {
IsDev bool
}{
IsDev: mode == buildModeDev,
}

f, err := os.Create(filepath.Join("ui", "index.html"))
if err != nil {
return fmt.Errorf("creating file: %w", err)
}

if err := tmplt.Execute(f, v); err != nil {
return fmt.Errorf("executing template: %w", err)
}

if err := f.Close(); err != nil {
return fmt.Errorf("closing file: %w", err)
}

if mode == buildModeDev {
return nil
}

fmt.Println("Installing UI deps...")

// TODO: only install if package.json has changed
cmd := exec.Command("npm", "ci")
cmd.Dir = "ui"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return fmt.Errorf("installing UI deps: %w", err)
}

fmt.Println("Generating assets...")

cmd = exec.Command("npm", "run", "build")
cmd.Dir = "ui"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

return cmd.Run()
}

func build(mode buildMode) error {
if err := ui(mode); err != nil {
return fmt.Errorf("build UI: %w", err)
}

buildDate := time.Now().UTC().Format(time.RFC3339)
buildArgs := make([]string, 0)

switch mode {
case buildModeProd:
buildArgs = append(buildArgs, "-tags", "assets")
}

gitCommit, err := sh.Output("git", "rev-parse", "HEAD")
if err != nil {
return fmt.Errorf("failed to get git commit: %w", err)
return fmt.Errorf("getting git commit: %w", err)
}

buildArgs := append([]string{"build", "-trimpath", "-ldflags", fmt.Sprintf("-X main.commit=%s -X main.date=%s", gitCommit, buildDate)}, args...)
buildArgs = append([]string{"build", "-trimpath", "-ldflags", fmt.Sprintf("-X main.commit=%s -X main.date=%s", gitCommit, buildDate)}, buildArgs...)
buildArgs = append(buildArgs, "-o", "./bin/flipt", "./cmd/flipt/")

return sh.RunV("go", buildArgs...)
Expand All @@ -118,17 +193,13 @@ func Clean() error {
fmt.Println("Cleaning...")

if err := sh.RunV("go", "mod", "tidy"); err != nil {
return fmt.Errorf("failed to tidy go.mod: %w", err)
}

if err := sh.RunV("go", "clean", "-i", "./..."); err != nil {
return fmt.Errorf("failed to clean cache: %w", err)
return fmt.Errorf("tidying go.mod: %w", err)
}

clean := []string{"dist/*", "pkg/*", "bin/*", "ui/dist"}
for _, dir := range clean {
if err := os.RemoveAll(dir); err != nil {
return fmt.Errorf("failed to remove dir %q: %w", dir, err)
return fmt.Errorf("removing dir %q: %w", dir, err)
}
}

Expand Down Expand Up @@ -157,7 +228,7 @@ func Fmt() error {
return filepath.Ext(path) == ".go"
})
if err != nil {
return fmt.Errorf("failed to find files: %w", err)
return fmt.Errorf("finding files: %w", err)
}

args := append([]string{"-w"}, files...)
Expand All @@ -169,20 +240,12 @@ func Lint() error {
fmt.Println("Linting...")

if err := sh.RunV("golangci-lint", "run"); err != nil {
return fmt.Errorf("failed to lint: %w", err)
return fmt.Errorf("linting: %w", err)
}

return sh.RunV("buf", "lint")
}

// Prep prepares the project for building
func Prep() error {
fmt.Println("Preparing...")
mg.Deps(Clean)
mg.Deps(UI.Build)
return nil
}

// Proto generates protobuf files and gRPC stubs
func Proto() error {
mg.Deps(Bootstrap)
Expand All @@ -205,33 +268,6 @@ func Test() error {
return sh.RunWithV(env, "go", "test", "-v", "-covermode=atomic", "-count=1", "-coverprofile=coverage.txt", "-timeout=60s", "./...")
}

type UI mg.Namespace

// Build generates UI assets
func (u UI) Build() error {
mg.Deps(u.Deps)
fmt.Println("Generating assets...")

cmd := exec.Command("npm", "run", "build")
cmd.Dir = "ui"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr

return cmd.Run()
}

// Deps installs UI deps
func (u UI) Deps() error {
fmt.Println("Installing UI deps...")

// TODO: only install if package.json has changed
cmd := exec.Command("npm", "ci")
cmd.Dir = "ui"
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

// findFilesRecursive recursively traverses from the CWD and invokes the given
// match function on each regular file to determine if the given path should be
// returned as a match. It ignores files in .git directories.
Expand Down
3 changes: 3 additions & 0 deletions ui/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,6 @@ dist-ssr
/test-results/
/playwright-report/
/playwright/.cache/

# Generated
index.html
2 changes: 1 addition & 1 deletion ui/dev.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ package ui
import "embed"

var (
//go:embed dev.html
//go:embed index.html
UI embed.FS
Mount = "."
)
21 changes: 0 additions & 21 deletions ui/dev.html

This file was deleted.

35 changes: 25 additions & 10 deletions ui/index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
<!DOCTYPE html>
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is simply to get unit tests/etc to pass as our build expects this file to be here. it will get overwritten during the mage or mage dev tasks and is added to .gitignore so any changes will not be committed to git

<html lang="en" class="h-full w-auto bg-white">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Flipt</title>
</head>
<body class="h-full antialiased">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Flipt</title>

<script type="module">
import RefreshRuntime from 'http://localhost:5173/@react-refresh';
RefreshRuntime.injectIntoGlobalHook(window);
window.$RefreshReg$ = () => { };
window.$RefreshSig$ = () => (type) => type;
window.__vite_plugin_react_preamble_installed__ = true;
</script>
<script type="module" src="http://localhost:5173/@vite/client"></script>

</head>

<body class="h-full antialiased">
<div id="root"></div>

<script type="module" src="http://localhost:5173/src/main.tsx"></script>

</body>

</html>
30 changes: 30 additions & 0 deletions ui/index.html.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en" class="h-full w-auto bg-white">

<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Flipt</title>
{{if .IsDev}}
<script type="module">
import RefreshRuntime from 'http://localhost:5173/@react-refresh';
RefreshRuntime.injectIntoGlobalHook(window);
window.$RefreshReg$ = () => { };
window.$RefreshSig$ = () => (type) => type;
window.__vite_plugin_react_preamble_installed__ = true;
</script>
<script type="module" src="http://localhost:5173/@vite/client"></script>
{{end}}
</head>

<body class="h-full antialiased">
<div id="root"></div>
{{if .IsDev}}
<script type="module" src="http://localhost:5173/src/main.tsx"></script>
{{else}}
<script type="module" src="/src/main.tsx"></script>
{{end}}
</body>

</html>