Skip to content

Commit

Permalink
feat: outdated version notifier (spawn + local-ic) (#156)
Browse files Browse the repository at this point in the history
* initial version checker notifications

* move plugins to their own file

* move ver checks to dedicated. Check both spawn & local-ic

* fix & simplify integration test

* debugging better
  • Loading branch information
Reecepbcups authored Jul 22, 2024
1 parent 234a78a commit e8c1755
Show file tree
Hide file tree
Showing 6 changed files with 398 additions and 139 deletions.
24 changes: 2 additions & 22 deletions cmd/spawn/local-interchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package main

import (
"os"
"os/exec"
"path"

"github.com/spf13/cobra"

Expand All @@ -16,6 +14,7 @@ const (

func init() {
LocalICCmd.Flags().Bool(FlagLocationPath, false, "print the location of local-ic binary")

}

// ---
Expand All @@ -34,12 +33,7 @@ var LocalICCmd = &cobra.Command{

logger := GetLogger()

loc := whereIsLocalICInstalled()
if loc == "" {
logger.Error("local-ic not found. Please run `make get-localic`")
return
}

loc := spawn.WhereIsBinInstalled("local-ic")
if debugBinaryLoc {
logger.Debug("local-ic binary", "location", loc)
return
Expand All @@ -61,17 +55,3 @@ var LocalICCmd = &cobra.Command{
}
},
}

func whereIsLocalICInstalled() string {
for _, path := range []string{"local-ic", path.Join("bin", "local-ic"), path.Join("local-interchain", "localic")} {
if _, err := os.Stat(path); err == nil {
return path
}
}

if path, err := exec.LookPath("local-ic"); err == nil {
return path
}

return ""
}
165 changes: 50 additions & 115 deletions cmd/spawn/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,51 @@ package main

import (
"fmt"
"io/fs"
"log"
"log/slog"
"os"
"os/exec"
"path"
"strings"
"time"

"github.com/lmittmann/tint"
"github.com/mattn/go-isatty"
"github.com/rollchains/spawn/spawn"
"github.com/spf13/cobra"
)

// Set in the makefile ld_flags on compile
var SpawnVersion = ""

var LogLevelFlag = "log-level"
var (
// Set in the makefile ld_flags on compile
SpawnVersion = ""
LogLevelFlag = "log-level"
rootCmd = &cobra.Command{
Use: "spawn",
Short: "Entry into the Interchain | Contact us: [email protected]",
CompletionOptions: cobra.CompletionOptions{
HiddenDefaultCmd: false,
},
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
}
},
}
)

func main() {
outOfDateChecker()

rootCmd.AddCommand(newChain)
rootCmd.AddCommand(LocalICCmd)
rootCmd.AddCommand(versionCmd)
rootCmd.AddCommand(&cobra.Command{
Use: "version",
Short: "Print the version number of spawn",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(SpawnVersion)
},
})
rootCmd.AddCommand(ModuleCmd())
rootCmd.AddCommand(ProtoServiceGenerate())
rootCmd.AddCommand(DocsCmd)
rootCmd.AddCommand(contactCmd)

applyPluginCmds()

Expand Down Expand Up @@ -59,123 +76,41 @@ func GetLogger() *slog.Logger {
return slog.Default()
}

var PluginsCmd = &cobra.Command{
Use: "plugins",
Short: "Spawn Plugins",
Aliases: []string{"plugin", "plug", "pl"},
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
}
},
}
// outOfDateChecker checks if binaries are up to date and logs if they are not.
// if not, it will prompt the user every command they run with spawn until they update.
// else, it will wait 24h+ before checking again.
func outOfDateChecker() {
logger := GetLogger()

func applyPluginCmds() {
for name, abspath := range loadPlugins() {
name := name
abspath := abspath
if !spawn.DoOutdatedNotificationRunCheck(logger) {
return
}

info, err := ParseCobraCLICmd(abspath)
for _, program := range []string{"local-ic", "spawn"} {
releases, err := spawn.GetLatestGithubReleases(spawn.BinaryToGithubAPI[program])
if err != nil {
GetLogger().Warn("error parsing the CLI commands from the plugin", "name", name, "error", err)
continue
logger.Error("Error getting latest local-ic releases", "err", err)
return
}
latest := releases[0].TagName

execCmd := &cobra.Command{
Use: name,
Short: info.Description,
Run: func(cmd *cobra.Command, args []string) {
output, err := exec.Command(abspath, args...).CombinedOutput()
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(string(output))
},
}
PluginsCmd.AddCommand(execCmd)
}

rootCmd.AddCommand(PluginsCmd)
}

// returns name and path
func loadPlugins() map[string]string {
p := make(map[string]string)

logger := GetLogger()

homeDir, err := os.UserHomeDir()
if err != nil {
panic(err)
}

pluginsDir := path.Join(homeDir, ".spawn", "plugins")
current := spawn.GetLocalVersion(logger, program, latest)
if spawn.OutOfDateCheckLog(logger, program, current, latest) {
// write check to -24h from now to spam the user until it's resolved.

d := os.DirFS(pluginsDir)
if _, err := d.Open("."); err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(pluginsDir, 0755); err != nil {
panic(err)
file, err := spawn.GetLatestVersionCheckFile(logger)
if err != nil {
return
}
} else {
panic(err)
}
}

err = fs.WalkDir(d, ".", func(relPath string, d fs.DirEntry, e error) error {
if d.IsDir() {
return nil
}

// /home/username/.spawn/plugins/myplugin
absPath := path.Join(pluginsDir, relPath)
if err := spawn.WriteLastTimeToFile(logger, file, time.Now().Add(-spawn.RunCheckInterval)); err != nil {
logger.Error("Error writing last check file", "err", err)
return
}

// ensure path exist
if _, err := os.Stat(absPath); os.IsNotExist(err) {
logger.Error(fmt.Sprintf("Plugin %s does not exist. Skipping", absPath))
return nil
return
}

name := path.Base(absPath)
p[name] = absPath
return nil
})
if err != nil {
logger.Error(fmt.Sprintf("Error walking the path %s: %v", pluginsDir, err))
panic(err)
}

return p
}

var rootCmd = &cobra.Command{
Use: "spawn",
Short: "Entry into the Interchain",
CompletionOptions: cobra.CompletionOptions{
HiddenDefaultCmd: false,
},
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
}
},
}

var versionCmd = &cobra.Command{
Use: "version",
Short: "Print the version number of spawn",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(SpawnVersion)
},
}

var contactCmd = &cobra.Command{
Use: "email",
Aliases: []string{"contact"},
Short: "Reach out and connect with us!",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("Email us! [email protected]")
},
}

func parseLogLevelFromFlags() slog.Level {
Expand Down
5 changes: 3 additions & 2 deletions cmd/spawn/new_chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func TestDisabledGeneration(t *testing.T) {
}

// custom cases
// NOTE: block-explorer is disabled for all cases.
disabledCases := []disabledCase{
{
// by default ICS is used
Expand Down Expand Up @@ -70,14 +71,14 @@ func TestDisabledGeneration(t *testing.T) {

disabledCases = append(disabledCases, disabledCase{
Name: normalizedName,
Disabled: []string{f},
Disabled: []string{f, spawn.BlockExplorer},
})
}

for _, c := range disabledCases {
c := c
name := "spawnunittest" + c.Name
dc := c.Disabled
dc := append(c.Disabled, spawn.BlockExplorer)

fmt.Println("=====\ndisabled cases", name, dc)

Expand Down
101 changes: 101 additions & 0 deletions cmd/spawn/plugins.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package main

import (
"fmt"
"io/fs"
"log"
"os"
"os/exec"
"path"

"github.com/spf13/cobra"
)

var PluginsCmd = &cobra.Command{
Use: "plugins",
Short: "Spawn Plugins",
Aliases: []string{"plugin", "plug", "pl"},
Run: func(cmd *cobra.Command, args []string) {
if err := cmd.Help(); err != nil {
log.Fatal(err)
}
},
}

func applyPluginCmds() {
for name, abspath := range loadPlugins() {
name := name
abspath := abspath

info, err := ParseCobraCLICmd(abspath)
if err != nil {
GetLogger().Warn("error parsing the CLI commands from the plugin", "name", name, "error", err)
continue
}

execCmd := &cobra.Command{
Use: name,
Short: info.Description,
Run: func(cmd *cobra.Command, args []string) {
output, err := exec.Command(abspath, args...).CombinedOutput()
if err != nil {
fmt.Println(err.Error())
}
fmt.Println(string(output))
},
}
PluginsCmd.AddCommand(execCmd)
}

rootCmd.AddCommand(PluginsCmd)
}

// returns name and path
func loadPlugins() map[string]string {
p := make(map[string]string)

logger := GetLogger()

homeDir, err := os.UserHomeDir()
if err != nil {
panic(err)
}

pluginsDir := path.Join(homeDir, ".spawn", "plugins")

d := os.DirFS(pluginsDir)
if _, err := d.Open("."); err != nil {
if os.IsNotExist(err) {
if err := os.MkdirAll(pluginsDir, 0755); err != nil {
panic(err)
}
} else {
panic(err)
}
}

err = fs.WalkDir(d, ".", func(relPath string, d fs.DirEntry, e error) error {
if d.IsDir() {
return nil
}

// /home/username/.spawn/plugins/myplugin
absPath := path.Join(pluginsDir, relPath)

// ensure path exist
if _, err := os.Stat(absPath); os.IsNotExist(err) {
logger.Error(fmt.Sprintf("Plugin %s does not exist. Skipping", absPath))
return nil
}

name := path.Base(absPath)
p[name] = absPath
return nil
})
if err != nil {
logger.Error(fmt.Sprintf("Error walking the path %s: %v", pluginsDir, err))
panic(err)
}

return p
}
5 changes: 5 additions & 0 deletions spawn/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ func ExecCommand(command string, args ...string) error {
return cmd.Run()
}

func ExecCommandWithOutput(command string, args ...string) ([]byte, error) {
cmd := exec.Command(command, args...)
return cmd.CombinedOutput()
}

func (cfg *NewChainConfig) GitInitNewProjectRepo() {
if err := ExecCommand("git", "init", cfg.ProjectName, "--quiet"); err != nil {
cfg.Logger.Error("Error initializing git", "err", err)
Expand Down
Loading

0 comments on commit e8c1755

Please sign in to comment.