mirror of
https://github.com/pocketbase/pocketbase.git
synced 2024-11-21 13:35:49 +02:00
tweaked automigrate to check for git status and extracted the base flags from the plugins
This commit is contained in:
parent
8c9b657132
commit
675d459137
4
.gitignore
vendored
4
.gitignore
vendored
@ -6,10 +6,6 @@
|
||||
# goreleaser builds folder
|
||||
/.builds/
|
||||
|
||||
# examples app directories
|
||||
pb_data
|
||||
pb_public
|
||||
|
||||
# tests coverage
|
||||
coverage.out
|
||||
|
||||
|
241
cmd/migrate.go
241
cmd/migrate.go
@ -1,241 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/migrations/logs"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/inflector"
|
||||
"github.com/pocketbase/pocketbase/tools/migrate"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// NewMigrateCommand creates and returns new command for handling DB migrations.
|
||||
func NewMigrateCommand(app core.App) *cobra.Command {
|
||||
desc := `
|
||||
Supported arguments are:
|
||||
- up - runs all available migrations.
|
||||
- down [number] - reverts the last [number] applied migrations.
|
||||
- create name [folder] - creates new migration template file.
|
||||
- collections [folder] - (Experimental) creates new migration file with the most recent local collections configuration.
|
||||
`
|
||||
var databaseFlag string
|
||||
|
||||
command := &cobra.Command{
|
||||
Use: "migrate",
|
||||
Short: "Executes DB migration scripts",
|
||||
ValidArgs: []string{"up", "down", "create", "collections"},
|
||||
Long: desc,
|
||||
Run: func(command *cobra.Command, args []string) {
|
||||
cmd := ""
|
||||
if len(args) > 0 {
|
||||
cmd = args[0]
|
||||
}
|
||||
|
||||
// additional commands
|
||||
// ---
|
||||
if cmd == "create" {
|
||||
if err := migrateCreateHandler(defaultMigrateCreateTemplate, args[1:]); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
if cmd == "collections" {
|
||||
if err := migrateCollectionsHandler(app, args[1:]); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
// ---
|
||||
|
||||
// normalize
|
||||
if databaseFlag != "logs" {
|
||||
databaseFlag = "db"
|
||||
}
|
||||
|
||||
connections := migrationsConnectionsMap(app)
|
||||
|
||||
runner, err := migrate.NewRunner(
|
||||
connections[databaseFlag].DB,
|
||||
connections[databaseFlag].MigrationsList,
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
if err := runner.Run(args...); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
command.PersistentFlags().StringVar(
|
||||
&databaseFlag,
|
||||
"database",
|
||||
"db",
|
||||
"specify the database connection to use (db or logs)",
|
||||
)
|
||||
|
||||
return command
|
||||
}
|
||||
|
||||
type migrationsConnection struct {
|
||||
DB *dbx.DB
|
||||
MigrationsList migrate.MigrationsList
|
||||
}
|
||||
|
||||
func migrationsConnectionsMap(app core.App) map[string]migrationsConnection {
|
||||
return map[string]migrationsConnection{
|
||||
"db": {
|
||||
DB: app.DB(),
|
||||
MigrationsList: migrations.AppMigrations,
|
||||
},
|
||||
"logs": {
|
||||
DB: app.LogsDB(),
|
||||
MigrationsList: logs.LogsMigrations,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// migrate create
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const defaultMigrateCreateTemplate = `package migrations
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/dbx"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
)
|
||||
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
// add up queries...
|
||||
|
||||
return nil
|
||||
}, func(db dbx.Builder) error {
|
||||
// add down queries...
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
`
|
||||
|
||||
func migrateCreateHandler(template string, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("Missing migration file name")
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
|
||||
var dir string
|
||||
if len(args) == 2 {
|
||||
dir = args[1]
|
||||
}
|
||||
if dir == "" {
|
||||
// If not specified, auto point to the default migrations folder.
|
||||
//
|
||||
// NB!
|
||||
// Since the create command makes sense only during development,
|
||||
// it is expected the user to be in the app working directory
|
||||
// and to be using `go run`
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir = path.Join(wd, "migrations")
|
||||
}
|
||||
|
||||
resultFilePath := path.Join(
|
||||
dir,
|
||||
fmt.Sprintf("%d_%s.go", time.Now().Unix(), inflector.Snakecase(name)),
|
||||
)
|
||||
|
||||
confirm := false
|
||||
prompt := &survey.Confirm{
|
||||
Message: fmt.Sprintf("Do you really want to create migration %q?", resultFilePath),
|
||||
}
|
||||
survey.AskOne(prompt, &confirm)
|
||||
if !confirm {
|
||||
fmt.Println("The command has been cancelled")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensure that migrations dir exist
|
||||
if err := os.MkdirAll(dir, os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.WriteFile(resultFilePath, []byte(template), 0644); err != nil {
|
||||
return fmt.Errorf("Failed to save migration file %q\n", resultFilePath)
|
||||
}
|
||||
|
||||
fmt.Printf("Successfully created file %q\n", resultFilePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// migrate collections
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
const collectionsMigrateCreateTemplate = `package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
)
|
||||
|
||||
// Auto generated migration with the most recent collections configuration.
|
||||
func init() {
|
||||
m.Register(func(db dbx.Builder) error {
|
||||
jsonData := ` + "`" + `%s` + "`" + `
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := json.Unmarshal([]byte(jsonData), &collections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return daos.New(db).ImportCollections(collections, true, nil)
|
||||
}, func(db dbx.Builder) error {
|
||||
// no revert since the configuration on the environment, on which
|
||||
// the migration was executed, could have changed via the UI/API
|
||||
return nil
|
||||
})
|
||||
}
|
||||
`
|
||||
|
||||
func migrateCollectionsHandler(app core.App, args []string) error {
|
||||
createArgs := []string{"collections_snapshot"}
|
||||
createArgs = append(createArgs, args...)
|
||||
|
||||
dao := daos.New(app.DB())
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := dao.CollectionQuery().OrderBy("created ASC").All(&collections); err != nil {
|
||||
return fmt.Errorf("Failed to fetch migrations list: %v", err)
|
||||
}
|
||||
|
||||
serialized, err := json.MarshalIndent(collections, "\t\t", "\t")
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to serialize collections list: %v", err)
|
||||
}
|
||||
|
||||
return migrateCreateHandler(
|
||||
fmt.Sprintf(collectionsMigrateCreateTemplate, string(serialized)),
|
||||
createArgs,
|
||||
)
|
||||
}
|
21
cmd/serve.go
21
cmd/serve.go
@ -10,8 +10,11 @@ import (
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/labstack/echo/v5/middleware"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/migrations/logs"
|
||||
"github.com/pocketbase/pocketbase/tools/migrate"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/crypto/acme"
|
||||
@ -38,7 +41,7 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
|
||||
// (or if this is the first time the init migration was executed)
|
||||
if err := app.RefreshSettings(); err != nil {
|
||||
color.Yellow("=====================================")
|
||||
color.Yellow("WARNING - Settings load error! \n%v", err)
|
||||
color.Yellow("WARNING: Settings load error! \n%v", err)
|
||||
color.Yellow("Fallback to the application defaults.")
|
||||
color.Yellow("=====================================")
|
||||
}
|
||||
@ -137,8 +140,22 @@ func NewServeCommand(app core.App, showStartBanner bool) *cobra.Command {
|
||||
return command
|
||||
}
|
||||
|
||||
type migrationsConnection struct {
|
||||
DB *dbx.DB
|
||||
MigrationsList migrate.MigrationsList
|
||||
}
|
||||
|
||||
func runMigrations(app core.App) error {
|
||||
connections := migrationsConnectionsMap(app)
|
||||
connections := []migrationsConnection{
|
||||
{
|
||||
DB: app.DB(),
|
||||
MigrationsList: migrations.AppMigrations,
|
||||
},
|
||||
{
|
||||
DB: app.LogsDB(),
|
||||
MigrationsList: logs.LogsMigrations,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range connections {
|
||||
runner, err := migrate.NewRunner(c.DB, c.MigrationsList)
|
||||
|
3
examples/base/.gitignore
vendored
Normal file
3
examples/base/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
pb_data/
|
||||
pb_public/
|
||||
pb_migrations/
|
@ -2,6 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
||||
"github.com/pocketbase/pocketbase"
|
||||
"github.com/pocketbase/pocketbase/plugins/jsvm"
|
||||
@ -12,19 +14,65 @@ import (
|
||||
func main() {
|
||||
app := pocketbase.New()
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Optional plugin flags:
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
var migrationsDir string
|
||||
app.RootCmd.PersistentFlags().StringVar(
|
||||
&migrationsDir,
|
||||
"migrationsDir",
|
||||
"",
|
||||
"the directory with the user defined migrations",
|
||||
)
|
||||
|
||||
var automigrate bool
|
||||
_, gitErr := exec.LookPath("git")
|
||||
app.RootCmd.PersistentFlags().BoolVar(
|
||||
&automigrate,
|
||||
"automigrate",
|
||||
gitErr == nil,
|
||||
"enable/disable auto migrations",
|
||||
)
|
||||
|
||||
var publicDir string
|
||||
app.RootCmd.PersistentFlags().StringVar(
|
||||
&publicDir,
|
||||
"publicDir",
|
||||
"",
|
||||
"the directory to serve static files",
|
||||
)
|
||||
|
||||
var indexFallback bool
|
||||
app.RootCmd.PersistentFlags().BoolVar(
|
||||
&indexFallback,
|
||||
"indexFallback",
|
||||
true,
|
||||
"fallback the request to index.html on missing static path (eg. when pretty urls are used with SPA)",
|
||||
)
|
||||
|
||||
app.RootCmd.ParseFlags(os.Args[1:])
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Plugins:
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// load js pb_migrations
|
||||
jsvm.MustRegisterMigrationsLoader(app, nil)
|
||||
jsvm.MustRegisterMigrationsLoader(app, &jsvm.MigrationsLoaderOptions{
|
||||
Dir: migrationsDir,
|
||||
})
|
||||
|
||||
// migrate command (with js templates)
|
||||
migratecmd.MustRegister(app, app.RootCmd, &migratecmd.Options{
|
||||
TemplateLang: migratecmd.TemplateLangJS,
|
||||
AutoMigrate: true,
|
||||
Automigrate: automigrate,
|
||||
Dir: migrationsDir,
|
||||
})
|
||||
|
||||
// pb_public dir
|
||||
publicdir.MustRegister(app, &publicdir.Options{
|
||||
FlagsCmd: app.RootCmd,
|
||||
IndexFallback: true,
|
||||
Dir: publicDir,
|
||||
IndexFallback: indexFallback,
|
||||
})
|
||||
|
||||
if err := app.Start(); err != nil {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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{}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
12
tests/app.go
12
tests/app.go
@ -18,7 +18,13 @@ type TestApp struct {
|
||||
|
||||
// EventCalls defines a map to inspect which app events
|
||||
// (and how many times) were triggered.
|
||||
//
|
||||
// The following events are not counted because they execute always:
|
||||
// - OnBeforeBootstrap
|
||||
// - OnAfterBootstrap
|
||||
// - OnBeforeServe
|
||||
EventCalls map[string]int
|
||||
|
||||
TestMailer *TestMailer
|
||||
}
|
||||
|
||||
@ -81,12 +87,6 @@ func NewTestApp(optTestDataDir ...string) (*TestApp, error) {
|
||||
TestMailer: &TestMailer{},
|
||||
}
|
||||
|
||||
// no need to count since this is executed always
|
||||
// t.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
// t.EventCalls["OnBeforeServe"]++
|
||||
// return nil
|
||||
// })
|
||||
|
||||
t.OnModelBeforeCreate().Add(func(e *core.ModelEvent) error {
|
||||
t.EventCalls["OnModelBeforeCreate"]++
|
||||
return nil
|
||||
|
@ -115,8 +115,11 @@ func (r *Runner) Up() ([]string, error) {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := m.Up(tx); err != nil {
|
||||
return fmt.Errorf("Failed to apply migration %s: %w", m.File, err)
|
||||
// ignore empty Up action
|
||||
if m.Up != nil {
|
||||
if err := m.Up(tx); err != nil {
|
||||
return fmt.Errorf("Failed to apply migration %s: %w", m.File, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.saveAppliedMigration(tx, m.File); err != nil {
|
||||
@ -155,8 +158,11 @@ func (r *Runner) Down(toRevertCount int) ([]string, error) {
|
||||
break
|
||||
}
|
||||
|
||||
if err := m.Down(tx); err != nil {
|
||||
return fmt.Errorf("Failed to revert migration %s: %w", m.File, err)
|
||||
// ignore empty Down action
|
||||
if m.Down != nil {
|
||||
if err := m.Down(tx); err != nil {
|
||||
return fmt.Errorf("Failed to revert migration %s: %w", m.File, err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.saveRevertedMigration(tx, m.File); err != nil {
|
||||
|
Loading…
Reference in New Issue
Block a user