From e05db8263e6665ef42d6cb5d4bea9313b9447153 Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Wed, 20 Nov 2024 10:29:21 -0500 Subject: [PATCH 1/6] Use yarn to create plugin frontend --- cmd/grafana-app-sdk/project.go | 62 +++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 12 deletions(-) diff --git a/cmd/grafana-app-sdk/project.go b/cmd/grafana-app-sdk/project.go index cc36e3b2..1f90ca7d 100644 --- a/cmd/grafana-app-sdk/project.go +++ b/cmd/grafana-app-sdk/project.go @@ -454,7 +454,7 @@ 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) @@ -462,7 +462,7 @@ func projectAddComponent(cmd *cobra.Command, args []string) error { 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) } @@ -485,12 +485,16 @@ type anyGenerator interface { *codegen.Generator[codegen.Kind] } -func addComponentOperator[G anyGenerator](projectRootPath string, generator G, selectors []string, groupKinds bool) error { +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) { @@ -511,7 +515,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 } @@ -521,7 +525,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 } @@ -613,30 +617,64 @@ func projectAddPluginAPI[G anyGenerator](generator G, repo, generatedAPIModelsPa // Frontend plugin // -func addComponentFrontend(projectRootPath string, pluginID string, promptForOverwrite bool) error { +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) + args := []string{"create", "@grafana/plugin", "--pluginType=app", "--hasBackend=true", "--pluginName=tmp", "--orgName=tmp"} + cmd := exec.Command("yarn", args...) + buf := bytes.Buffer{} + cmd.Stdout = &buf + 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()) return err } - err = writePluginConstants(filepath.Join(projectRootPath, "plugin/src/constants.ts"), pluginID) + + err = moveFiles("./tmp-tmp-app/", filepath.Join(projectRootPath, "plugin")) if err != nil { return err } - err = writePackageJSON(filepath.Join(projectRootPath, "plugin/package.json"), "NAME", "AUTHOR") + err = writePluginJSON(filepath.Join(projectRootPath, "plugin/src/plugin.json"), + fmt.Sprintf("%s-app", pluginID), "NAME", "AUTHOR", pluginID) if err != nil { return err } - return nil + 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 + } + 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 writePluginJSON(fullPath, id, name, author, slug string) error { From 4b3593c536a2b77f74fd9202bc49549d2edf57bc Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Wed, 20 Nov 2024 11:51:03 -0500 Subject: [PATCH 2/6] Check if yarn is installed when adding front-end component. --- cmd/grafana-app-sdk/project.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/cmd/grafana-app-sdk/project.go b/cmd/grafana-app-sdk/project.go index 1f90ca7d..a056fb4d 100644 --- a/cmd/grafana-app-sdk/project.go +++ b/cmd/grafana-app-sdk/project.go @@ -623,6 +623,10 @@ func addComponentFrontend(projectRootPath string, pluginID string) error { return fmt.Errorf("plugin-id is required") } + 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{} @@ -677,6 +681,12 @@ func moveFiles(srcDir, destDir string) error { }) } +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 { tmp, err := template.ParseFS(templates, "templates/plugin.json.tmpl") if err != nil { From ff9b42c8c234976d8f53296d8d4d97f83c8e7e6d Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Wed, 20 Nov 2024 12:15:47 -0500 Subject: [PATCH 3/6] Remove unused code for linter, print standard error as well as standard out when yarn command errors. --- cmd/grafana-app-sdk/project.go | 70 +++------------------------------- 1 file changed, 5 insertions(+), 65 deletions(-) diff --git a/cmd/grafana-app-sdk/project.go b/cmd/grafana-app-sdk/project.go index a056fb4d..1ab18b86 100644 --- a/cmd/grafana-app-sdk/project.go +++ b/cmd/grafana-app-sdk/project.go @@ -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", } @@ -485,6 +482,7 @@ type anyGenerator interface { *codegen.Generator[codegen.Kind] } +//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")) @@ -613,10 +611,9 @@ func projectAddPluginAPI[G anyGenerator](generator G, repo, generatedAPIModelsPa return nil } -// // Frontend plugin // - +//nolint:revive func addComponentFrontend(projectRootPath string, pluginID string) error { // Check plugin ID if pluginID == "" { @@ -630,7 +627,9 @@ func addComponentFrontend(projectRootPath string, pluginID string) error { 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 @@ -640,6 +639,7 @@ func addComponentFrontend(projectRootPath string, pluginID string) error { if err != nil { // Only print command output on error fmt.Println(buf.String()) + fmt.Println(ebuf.String()) return err } @@ -711,26 +711,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 { @@ -748,43 +728,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 -} From ee10eb6163e8e8f79a8a68c773101f1063a6a371 Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Wed, 20 Nov 2024 12:52:43 -0500 Subject: [PATCH 4/6] Use image ubuntu-24.04 instead of ubuntu-latest, as ubuntu-latest is 22, and uses node 18, whereas ubuntu-24.04 uses node 20. --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b7059f7..7ade0cbe 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -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 From b9b11055db049352a9f136a6faa47ce85385604e Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Wed, 20 Nov 2024 13:05:24 -0500 Subject: [PATCH 5/6] Handle merging directories in moveFiles. --- cmd/grafana-app-sdk/project.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cmd/grafana-app-sdk/project.go b/cmd/grafana-app-sdk/project.go index 1ab18b86..7b45c071 100644 --- a/cmd/grafana-app-sdk/project.go +++ b/cmd/grafana-app-sdk/project.go @@ -670,6 +670,17 @@ func moveFiles(srcDir, destDir string) error { 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 From 53a5da366b765380660933708493c01ff91174c3 Mon Sep 17 00:00:00 2001 From: Austin Pond Date: Thu, 21 Nov 2024 14:43:54 -0500 Subject: [PATCH 6/6] Remove go module, generated go code, and .github directory from yarn-generated plugin code. --- cmd/grafana-app-sdk/project.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/cmd/grafana-app-sdk/project.go b/cmd/grafana-app-sdk/project.go index 7b45c071..8c3ab7d5 100644 --- a/cmd/grafana-app-sdk/project.go +++ b/cmd/grafana-app-sdk/project.go @@ -643,6 +643,24 @@ func addComponentFrontend(projectRootPath string, pluginID string) error { return err } + // 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 = os.RemoveAll("./tmp-tmp-app/pkg") + if err != nil { + return err + } + 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