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

[CLI][codegen] Use yarn create @grafana/plugin to Generate Front-End Code #491

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
- name: Build
run: go build -v cmd/grafana-app-sdk/*.go
integration-test:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
# git checkout
- name: Checkout code
Expand Down
171 changes: 94 additions & 77 deletions cmd/grafana-app-sdk/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ import (
//go:embed templates/*.tmpl
var templates embed.FS

//go:embed templates/frontend-static/* templates/frontend-static/.config/*
var frontEndStaticFiles embed.FS

var projectCmd = &cobra.Command{
Use: "project",
}
Expand Down Expand Up @@ -454,15 +451,15 @@ func projectAddComponent(cmd *cobra.Command, args []string) error {
os.Exit(1)
}
case "frontend":
err = addComponentFrontend(path, pluginID, !overwrite)
err = addComponentFrontend(path, pluginID)
if err != nil {
fmt.Printf("%s\n", err.Error())
os.Exit(1)
}
case "operator":
switch format {
case FormatCUE:
err = addComponentOperator(path, generator.(*codegen.Generator[codegen.Kind]), selectors, kindGrouping == kindGroupingGroup)
err = addComponentOperator(path, generator.(*codegen.Generator[codegen.Kind]), selectors, kindGrouping == kindGroupingGroup, !overwrite)
default:
return fmt.Errorf("unknown kind format '%s'", format)
}
Expand All @@ -485,12 +482,17 @@ type anyGenerator interface {
*codegen.Generator[codegen.Kind]
}

func addComponentOperator[G anyGenerator](projectRootPath string, generator G, selectors []string, groupKinds bool) error {
//nolint:revive
func addComponentOperator[G anyGenerator](projectRootPath string, generator G, selectors []string, groupKinds bool, confirmOverwrite bool) error {
// Get the repo from the go.mod file
repo, err := getGoModule(filepath.Join(projectRootPath, "go.mod"))
if err != nil {
return err
}
var writeFileFunc = writeFile
if confirmOverwrite {
writeFileFunc = writeFileWithOverwriteConfirm
}

var files codejen.Files
switch cast := any(generator).(type) {
Expand All @@ -511,7 +513,7 @@ func addComponentOperator[G anyGenerator](projectRootPath string, generator G, s
return err
}
for _, f := range files {
err = writeFile(filepath.Join(projectRootPath, f.RelativePath), f.Data)
err = writeFileFunc(filepath.Join(projectRootPath, f.RelativePath), f.Data)
if err != nil {
return err
}
Expand All @@ -521,7 +523,7 @@ func addComponentOperator[G anyGenerator](projectRootPath string, generator G, s
if err != nil {
return err
}
err = writeFile(filepath.Join(projectRootPath, "cmd", "operator", "Dockerfile"), dockerfile)
err = writeFileFunc(filepath.Join(projectRootPath, "cmd", "operator", "Dockerfile"), dockerfile)
if err != nil {
return err
}
Expand Down Expand Up @@ -609,34 +611,109 @@ func projectAddPluginAPI[G anyGenerator](generator G, repo, generatedAPIModelsPa
return nil
}

//
// Frontend plugin
//

func addComponentFrontend(projectRootPath string, pluginID string, promptForOverwrite bool) error {
//nolint:revive
func addComponentFrontend(projectRootPath string, pluginID string) error {
// Check plugin ID
if pluginID == "" {
return fmt.Errorf("plugin-id is required")
}

err := writeStaticFrontendFiles(filepath.Join(projectRootPath, "plugin"), promptForOverwrite)
if !isCommandInstalled("yarn") {
return fmt.Errorf("yarn must be installed to add the frontend component")
}

args := []string{"create", "@grafana/plugin", "--pluginType=app", "--hasBackend=true", "--pluginName=tmp", "--orgName=tmp"}
cmd := exec.Command("yarn", args...)
buf := bytes.Buffer{}
ebuf := bytes.Buffer{}
cmd.Stdout = &buf
cmd.Stderr = &ebuf
err := cmd.Start()
if err != nil {
return err
}
err = writePluginJSON(filepath.Join(projectRootPath, "plugin/src/plugin.json"),
fmt.Sprintf("%s-app", pluginID), "NAME", "AUTHOR", pluginID)
fmt.Println("Creating plugin frontend using `\033[0;32myarn create @grafana/plugin\033[0m` (this may take a moment)...")
err = cmd.Wait()
if err != nil {
// Only print command output on error
fmt.Println(buf.String())
fmt.Println(ebuf.String())
return err
}
err = writePluginConstants(filepath.Join(projectRootPath, "plugin/src/constants.ts"), pluginID)

// Remove a few directories that get created which we don't actually want
err = os.RemoveAll("./tmp-tmp-app/.github")
if err != nil {
return err
}
err = writePackageJSON(filepath.Join(projectRootPath, "plugin/package.json"), "NAME", "AUTHOR")
err = os.RemoveAll("./tmp-tmp-app/pkg")
if err != nil {
return err
}
return nil
err = os.Remove("./tmp-tmp-app/go.mod")
if err != nil {
return err
}
err = os.Remove("./tmp-tmp-app/go.sum")
if err != nil {
return err
}
// Move the remaining contents into /plugin
err = moveFiles("./tmp-tmp-app/", filepath.Join(projectRootPath, "plugin"))
if err != nil {
return err
}
err = writePluginJSON(filepath.Join(projectRootPath, "plugin/src/plugin.json"),
fmt.Sprintf("%s-app", pluginID), "NAME", "AUTHOR", pluginID)
if err != nil {
return err
}
err = writePluginConstants(filepath.Join(projectRootPath, "plugin/src/constants.ts"), pluginID)
if err != nil {
return err
}
return os.Remove("./tmp-tmp-app")
}

func moveFiles(srcDir, destDir string) error {
return filepath.WalkDir(srcDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}

// Just move directories wholesale by renaming
if d.IsDir() {
if path == srcDir {
return nil
}
dst := filepath.Join(destDir, d.Name())
if _, serr := os.Stat(dst); serr == nil {
err := moveFiles(path, dst)
if err != nil {
return err
}
if err = os.Remove(path); err != nil {
return err
}
return fs.SkipDir
}
err = os.Rename(path, filepath.Join(destDir, d.Name()))
if err != nil {
return err
}
return fs.SkipDir
}

return os.Rename(path, filepath.Join(destDir, d.Name()))
})
}

func isCommandInstalled(command string) bool {
cmd := exec.Command("which", command)
err := cmd.Run()
return err == nil
}

func writePluginJSON(fullPath, id, name, author, slug string) error {
Expand All @@ -663,26 +740,6 @@ func writePluginJSON(fullPath, id, name, author, slug string) error {
return writeFile(fullPath, b.Bytes())
}

func writePackageJSON(fullPath, name, author string) error {
tmp, err := template.ParseFS(templates, "templates/package.json.tmpl")
if err != nil {
return err
}
data := struct {
PluginName string
PluginAuthor string
}{
PluginName: name,
PluginAuthor: author,
}
b := bytes.Buffer{}
err = tmp.Execute(&b, data)
if err != nil {
return err
}
return writeFile(fullPath, b.Bytes())
}

func writePluginConstants(fullPath, pluginID string) error {
tmp, err := template.ParseFS(templates, "templates/constants.ts.tmpl")
if err != nil {
Expand All @@ -700,43 +757,3 @@ func writePluginConstants(fullPath, pluginID string) error {
}
return writeFile(fullPath, b.Bytes())
}

func writeStaticFrontendFiles(pluginPath string, promptForOverwrite bool) error {
return writeStaticFiles(frontEndStaticFiles, "templates/frontend-static", pluginPath, promptForOverwrite)
}

type mergedFS interface {
fs.ReadDirFS
fs.ReadFileFS
}

//nolint:revive
func writeStaticFiles(fs mergedFS, readDir, writeDir string, promptForOverwrite bool) error {
files, err := fs.ReadDir(readDir)
if err != nil {
return err
}
for _, f := range files {
if f.IsDir() {
err = writeStaticFiles(fs, filepath.Join(readDir, f.Name()), filepath.Join(writeDir, f.Name()),
promptForOverwrite)
if err != nil {
return err
}
continue
}
b, err := fs.ReadFile(filepath.Join(readDir, f.Name()))
if err != nil {
return err
}
if promptForOverwrite {
err = writeFileWithOverwriteConfirm(filepath.Join(writeDir, f.Name()), b)
} else {
err = writeFile(filepath.Join(writeDir, f.Name()), b)
}
if err != nil {
return err
}
}
return nil
}
Loading