add a simple 'autofix' - most of the users need just an update

This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-09-03 21:13:20 +03:00
parent 2a4ce876b6
commit 48c00f7cc2
No known key found for this signature in database
GPG Key ID: 5DBE766BD26A54E7
2 changed files with 223 additions and 9 deletions

193
autofix.go Normal file
View File

@ -0,0 +1,193 @@
package iris
import (
"archive/zip"
"bytes"
stdContext "context"
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"time"
"github.com/kataras/golog"
)
const defaultModuleName = "app"
// simple function does not uses AST, it simply replaces import paths,
// creates a go.mod file if not exists and then run the `go mod tidy`
// command to remove old dependencies and install the new ones.
// It does NOT replaces breaking changes.
// The developer SHOULD visit the changelog(HISTORY.md) in order to learn
// everything about the new features and any breaking changes that comes with it.
func tryFix() error {
wdir, err := filepath.Abs(".") // should return the current directory (on both go run & executable).
if err != nil {
return fmt.Errorf("can not resolve current working directory: %w", err)
}
// First of all, backup the current project,
// so any changes can be reverted by the end developer.
backupDest := wdir + "_irisbckp.zip"
golog.Infof("Backup <%s> to <%s>", wdir, backupDest)
err = zipDir(wdir, backupDest)
if err != nil {
return fmt.Errorf("backup dir: %w", err)
}
// go module.
goModFile := filepath.Join(wdir, "go.mod")
if !fileExists(goModFile) {
golog.Warnf("Project is not a go module. Executing <go.mod init app>")
f, err := os.Create(goModFile)
if err != nil {
return fmt.Errorf("go.mod: %w", os.ErrNotExist)
}
fmt.Fprintf(f, "module %s\ngo 1.15\n", defaultModuleName)
f.Close()
}
// contnets replacements.
golog.Infof("Updating...") // note: we will not replace GOPATH project paths.
err = replaceDirContents(wdir, map[string]string{
`"github.com/kataras/iris`: `"github.com/kataras/iris/v12`,
// Note: we could use
// regexp's FindAllSubmatch, take the dir part and replace
// any HandleDir and e.t.c, but we are not going to do this.
// Look the comment of the tryFix() function.
})
if err != nil {
return fmt.Errorf("replace import paths: %w", err)
}
commands := []string{
// "go clean --modcache",
"go env -w GOPROXY=https://goproxy.cn,https://gocenter.io,https://goproxy.io,direct",
"go mod tidy",
}
for _, c := range commands {
if err = runCmd(wdir, c); err != nil {
// print out the command, especially
// with go env -w the user should know it.
// We use that because many of our users are living in China,
// which the default goproxy is blocked).
golog.Infof("$ %s", c)
return fmt.Errorf("command <%s>: %w", c, err)
}
}
return nil
}
func fileExists(path string) bool {
stat, err := os.Stat(path)
if err != nil {
return os.IsExist(err)
}
return !stat.IsDir() && stat.Mode().IsRegular()
}
func runCmd(wdir, c string) error {
ctx, cancel := stdContext.WithTimeout(stdContext.Background(), 2*time.Minute)
defer cancel()
parts := strings.Split(c, " ")
name, args := parts[0], parts[1:]
cmd := exec.CommandContext(ctx, name, args...)
// cmd.Path = wdir
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
// zipDir zips a directory, recursively.
// It accepts a source directory and a destination zip file.
func zipDir(src, dest string) error {
folderName := filepath.Base(src)
file, err := os.Create(dest)
if err != nil {
return err
}
defer file.Close()
w := zip.NewWriter(file)
defer w.Close()
walkFunc := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
relPath := filepath.Join(folderName, strings.TrimPrefix(path, src))
f, err := w.Create(relPath)
if err != nil {
return err
}
_, err = io.Copy(f, file)
return err
}
return filepath.Walk(src, walkFunc)
}
func replaceDirContents(target string, replacements map[string]string) error {
walkFunc := func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() || !info.Mode().IsRegular() {
return nil
}
file, err := os.OpenFile(path, os.O_RDWR, 0666)
if err != nil {
return err
}
defer file.Close()
contents, ioErr := ioutil.ReadAll(file)
if ioErr != nil {
return ioErr
}
replaced := false
for oldContent, newContent := range replacements {
newContents := bytes.ReplaceAll(contents, []byte(oldContent), []byte(newContent))
if len(newContents) > 0 {
replaced = true
contents = newContents[0:]
}
}
if replaced {
file.Truncate(0)
file.Seek(0, 0)
_, err = file.Write(contents)
return err
}
return nil
}
return filepath.Walk(target, walkFunc)
}

39
iris.go
View File

@ -22,6 +22,7 @@ import (
requestLogger "github.com/kataras/iris/middleware/logger" requestLogger "github.com/kataras/iris/middleware/logger"
"github.com/kataras/iris/middleware/recover" "github.com/kataras/iris/middleware/recover"
"github.com/kataras/iris/view" "github.com/kataras/iris/view"
"github.com/kataras/pio"
"github.com/kataras/golog" "github.com/kataras/golog"
"github.com/kataras/tunnel" "github.com/kataras/tunnel"
@ -31,18 +32,38 @@ import (
const Version = "stale" const Version = "stale"
func init() { func init() {
golog.Fatal(`You have installed an invalid version. Install with: fmt.Println(`You have installed an invalid version. Install with:
go get -u github.com/kataras/iris/v12@latest go get -u github.com/kataras/iris/v12@latest
If your Open Source project depends on that pre-go1.9 version please open an issue If your Open Source project depends on that pre-go1.9 version please open an issue
at https://github.com/kataras/iris/issues/new and share your repository with us, at https://github.com/kataras/iris/issues/new and share your repository with us,
we will upgrade your project's code base to the latest version for free. we will upgrade your project's code base to the latest version for free.
If you have a commercial project that you cannot share publically, please contact with If you have a commercial project that you cannot share publically, please contact with
@kataras at https://chat.iris-go.com. Assistance will be provided to you and your colleagues @kataras at https://chat.iris-go.com. Assistance will be provided to you and your colleagues
for free. for free.
`) `)
fmt.Print("Run ")
pio.WriteRich(os.Stdout, "autofix", pio.Green, pio.Underline)
fmt.Print("? (Y/n): ")
var input string
_, err := fmt.Scanln(&input)
if err != nil {
golog.Fatalf("can not take input from user: %v", err)
}
input = strings.ToLower(input)
if input == "" || input == "y" {
err := tryFix()
if err != nil {
golog.Fatalf("autofix: %v", err)
}
golog.Infof("OK. Restart the application manually now.")
os.Exit(0)
} else {
os.Exit(-1)
}
} }
// Byte unit helpers. // Byte unit helpers.