tweaked automigrate to check for git status and extracted the base flags from the plugins

This commit is contained in:
Gani Georgiev
2022-11-26 22:33:27 +02:00
parent 8c9b657132
commit 675d459137
11 changed files with 170 additions and 367 deletions
+3 -2
View File
@@ -13,8 +13,9 @@ import (
// MigrationsLoaderOptions defines optional struct to customize the default plugin behavior.
type MigrationsLoaderOptions struct {
// Dir is the app migrations directory from where the js files will be loaded
// (default to pb_data/migrations)
// Dir specifies the directory with the JS migrations.
//
// If not set it fallbacks to a relative "pb_data/../pb_migrations" directory.
Dir string
}
+33 -47
View File
@@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"sort"
"strings"
@@ -17,23 +18,10 @@ import (
)
const migrationsTable = "_migrations"
const automigrateSuffix = "_automigrate"
// tidyMigrationsTable cleanups the migrations table by removing all
// entries with deleted migration files.
func (p *plugin) tidyMigrationsTable() error {
names, filesErr := p.getAllMigrationNames()
if filesErr != nil {
return fmt.Errorf("failed to fetch migration files list: %v", filesErr)
}
_, tidyErr := p.app.Dao().DB().Delete(migrationsTable, dbx.NotIn("file", list.ToInterfaceSlice(names)...)).Execute()
if tidyErr != nil {
return fmt.Errorf("failed to delete last automigrates from the db: %v", tidyErr)
}
return nil
}
// onCollectionChange handles the automigration snapshot generation on
// collection change event (create/update/delete).
func (p *plugin) onCollectionChange() func(*core.ModelEvent) error {
return func(e *core.ModelEvent) error {
if e.Model.TableName() != "_collections" {
@@ -48,35 +36,11 @@ func (p *plugin) onCollectionChange() func(*core.ModelEvent) error {
return errors.New("missing collections to automigrate")
}
names, err := p.getAllMigrationNames()
oldFiles, err := p.getAllMigrationNames()
if err != nil {
return fmt.Errorf("failed to fetch migration files list: %v", err)
}
// delete last consequitive automigrates
lastAutomigrates := []string{}
for i := len(names) - 1; i >= 0; i-- {
migrationFile := names[i]
if !strings.Contains(migrationFile, "_automigrate.") {
break
}
lastAutomigrates = append(lastAutomigrates, migrationFile)
}
if len(lastAutomigrates) > 0 {
// delete last automigrates from the db
_, err := p.app.Dao().DB().Delete(migrationsTable, dbx.In("file", list.ToInterfaceSlice(lastAutomigrates)...)).Execute()
if err != nil {
return fmt.Errorf("failed to delete last automigrates from the db: %v", err)
}
// delete last automigrates from the filesystem
for _, f := range lastAutomigrates {
if err := os.Remove(filepath.Join(p.options.Dir, f)); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to delete last automigrates from the filesystem: %v", err)
}
}
}
var template string
var templateErr error
if p.options.TemplateLang == TemplateLangJS {
@@ -88,10 +52,6 @@ func (p *plugin) onCollectionChange() func(*core.ModelEvent) error {
return fmt.Errorf("failed to resolve template: %v", templateErr)
}
// add a comment to not edit the template
template = ("// Do not edit by hand since this file is autogenerated and may get overwritten.\n" +
"// If you want to do further changes, create a new non '_automigrate' file instead.\n" + template)
appliedTime := time.Now().Unix()
fileDest := filepath.Join(p.options.Dir, fmt.Sprintf("%d_automigrate.%s", appliedTime, p.options.TemplateLang))
@@ -100,11 +60,37 @@ func (p *plugin) onCollectionChange() func(*core.ModelEvent) error {
return fmt.Errorf("failed to create migration dir: %v", err)
}
return os.WriteFile(fileDest, []byte(template), 0644)
if err := os.WriteFile(fileDest, []byte(template), 0644); err != nil {
return fmt.Errorf("failed to save automigrate file: %v", err)
}
// remove the old untracked automigrate file
// (only if the last one was automigrate!)
if len(oldFiles) > 0 && strings.HasSuffix(oldFiles[len(oldFiles)-1], automigrateSuffix+"."+p.options.TemplateLang) {
olfName := oldFiles[len(oldFiles)-1]
oldPath := filepath.Join(p.options.Dir, olfName)
isUntracked := exec.Command(p.options.GitPath, "ls-files", "--error-unmatch", oldPath).Run() != nil
if isUntracked {
// delete the old automigrate from the db if it was already applied
_, err := p.app.Dao().DB().Delete(migrationsTable, dbx.HashExp{"file": olfName}).Execute()
if err != nil {
return fmt.Errorf("failed to delete last applied automigrate from the migration db: %v", err)
}
// delete the old automigrate file from the filesystem
if err := os.Remove(oldPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to delete last automigrates from the filesystem: %v", err)
}
}
}
return nil
}
}
// getAllMigrationNames return both applied and new local migration file names.
// getAllMigrationNames return sorted slice with both applied and new
// local migration file names.
func (p *plugin) getAllMigrationNames() ([]string, error) {
names := []string{}
+44 -36
View File
@@ -4,11 +4,13 @@ import (
"fmt"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"time"
"github.com/AlecAivazis/survey/v2"
"github.com/fatih/color"
"github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models"
@@ -17,10 +19,23 @@ import (
"github.com/spf13/cobra"
)
// Options defines optional struct to customize the default plugin behavior.
type Options struct {
Dir string // the directory with user defined migrations
AutoMigrate bool
// Dir specifies the directory with the user defined migrations.
//
// If not set it fallbacks to a relative "pb_data/../pb_migrations" (for js)
// or "pb_data/../migrations" (for go) directory.
Dir string
// Automigrate specifies whether to enable automigrations.
Automigrate bool
// TemplateLang specifies the template language to use when
// generating migrations - js or go (default).
TemplateLang string
// GitPath is the git cmd binary path (default to just "git").
GitPath string
}
type plugin struct {
@@ -55,25 +70,24 @@ func Register(app core.App, rootCmd *cobra.Command, options *Options) error {
}
}
if p.options.GitPath == "" {
p.options.GitPath = "git"
}
// attach the migrate command
if rootCmd != nil {
rootCmd.AddCommand(p.createCommand())
}
// watch for collection changes
if p.options.AutoMigrate {
// @todo replace with AfterBootstrap
p.app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
if err := p.tidyMigrationsTable(); err != nil && p.app.IsDebug() {
log.Println("Failed to tidy the migrations table.")
}
return nil
})
p.app.OnModelAfterCreate().Add(p.onCollectionChange())
p.app.OnModelAfterUpdate().Add(p.onCollectionChange())
p.app.OnModelAfterDelete().Add(p.onCollectionChange())
if p.options.Automigrate {
if _, err := exec.LookPath(p.options.GitPath); err != nil {
color.Yellow("WARNING: Automigrate cannot be enabled because %s is not installed or accessable.", p.options.GitPath)
} else {
p.app.OnModelAfterCreate().Add(p.onCollectionChange())
p.app.OnModelAfterUpdate().Add(p.onCollectionChange())
p.app.OnModelAfterDelete().Add(p.onCollectionChange())
}
}
return nil
@@ -81,10 +95,10 @@ func Register(app core.App, rootCmd *cobra.Command, options *Options) error {
func (p *plugin) createCommand() *cobra.Command {
const cmdDesc = `Supported arguments are:
- up - runs all available migrations.
- down [number] - reverts the last [number] applied migrations.
- create name [folder] - creates new blank migration template file.
- collections [folder] - creates new migration file with the latest local collections snapshot (similar to the automigrate but allows editing).
- up - runs all available migrations
- down [number] - reverts the last [number] applied migrations
- create name [folder] - creates new blank migration template file
- collections [folder] - creates new migration file with the latest local collections snapshot (similar to the automigrate but allows editing)
`
command := &cobra.Command{
@@ -98,30 +112,24 @@ func (p *plugin) createCommand() *cobra.Command {
cmd = args[0]
}
// additional commands
// ---
if cmd == "create" {
switch cmd {
case "create":
if err := p.migrateCreateHandler("", args[1:]); err != nil {
log.Fatal(err)
}
return
}
if cmd == "collections" {
case "collections":
if err := p.migrateCollectionsHandler(args[1:]); err != nil {
log.Fatal(err)
}
return
}
// ---
default:
runner, err := migrate.NewRunner(p.app.DB(), migrations.AppMigrations)
if err != nil {
log.Fatal(err)
}
runner, err := migrate.NewRunner(p.app.DB(), migrations.AppMigrations)
if err != nil {
log.Fatal(err)
}
if err := runner.Run(args...); err != nil {
log.Fatal(err)
if err := runner.Run(args...); err != nil {
log.Fatal(err)
}
}
},
}
-21
View File
@@ -13,13 +13,11 @@ import (
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"github.com/spf13/cobra"
)
type Options struct {
Dir string
IndexFallback bool
FlagsCmd *cobra.Command
}
type plugin struct {
@@ -46,24 +44,6 @@ func Register(app core.App, options *Options) error {
options.Dir = defaultPublicDir()
}
if options.FlagsCmd != nil {
// add "--publicDir" option flag
options.FlagsCmd.PersistentFlags().StringVar(
&options.Dir,
"publicDir",
options.Dir,
"the directory to serve static files",
)
// add "--indexFallback" option flag
options.FlagsCmd.PersistentFlags().BoolVar(
&options.IndexFallback,
"indexFallback",
options.IndexFallback,
"fallback the request to index.html on missing static path (eg. when pretty urls are used with SPA)",
)
}
p.app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
// serves static files from the provided public dir (if exists)
e.Router.GET("/*", apis.StaticDirectoryHandler(os.DirFS(options.Dir), options.IndexFallback))
@@ -79,6 +59,5 @@ func defaultPublicDir() string {
// most likely ran with go run
return "./pb_public"
}
return filepath.Join(os.Args[0], "../pb_public")
}