Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion apps/cli-go/internal/db/diff/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/spf13/afero"
"github.com/supabase/cli/internal/db/start"
"github.com/supabase/cli/internal/utils"
configpkg "github.com/supabase/cli/pkg/config"
"github.com/supabase/cli/pkg/migration"
"github.com/supabase/cli/pkg/parser"
)
Expand Down Expand Up @@ -70,7 +71,11 @@ func loadDeclaredSchemas(fsys afero.Fs) ([]string, error) {
}
}
if schemas := utils.Config.Db.Migrations.SchemaPaths; len(schemas) > 0 {
return schemas.Files(afero.NewIOFS(fsys))
return schemas.Files(
afero.NewIOFS(fsys),
configpkg.WithSkipEmptyGlobs(),
configpkg.WithErrorOnAllSkippedGlobs(),
)
}
if exists, err := afero.DirExists(fsys, utils.SchemasDir); err != nil {
return nil, errors.Errorf("failed to check schemas: %w", err)
Expand Down
49 changes: 49 additions & 0 deletions apps/cli-go/internal/db/diff/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,3 +409,52 @@ func TestLoadSchemas(t *testing.T) {
assert.NoError(t, err)
assert.ElementsMatch(t, expected, schemas)
}

func TestLoadSchemasSkipsEmptySchemaPathGlobs(t *testing.T) {
fsys := afero.NewMemMapFs()
matched := filepath.Join(utils.SupabaseDirPath, "schemas", "tables", "players.sql")
require.NoError(t, afero.WriteFile(fsys, matched, nil, 0644))
utils.Config.Db.Migrations.SchemaPaths = []string{
filepath.Join(utils.SupabaseDirPath, "schemas", "tables", "*.sql"),
filepath.Join(utils.SupabaseDirPath, "schemas", "materialized_views", "*.sql"),
}
t.Cleanup(func() {
utils.Config.Db.Migrations.SchemaPaths = nil
})

schemas, err := loadDeclaredSchemas(fsys)

assert.NoError(t, err)
assert.Equal(t, []string{filepath.ToSlash(matched)}, schemas)
}

func TestLoadSchemasErrorsOnMissingLiteralSchemaPath(t *testing.T) {
fsys := afero.NewMemMapFs()
utils.Config.Db.Migrations.SchemaPaths = []string{
filepath.Join(utils.SupabaseDirPath, "schemas", "tables", "players.sql"),
}
t.Cleanup(func() {
utils.Config.Db.Migrations.SchemaPaths = nil
})

schemas, err := loadDeclaredSchemas(fsys)

assert.ErrorContains(t, err, "no files matched pattern")
assert.Empty(t, schemas)
}

func TestLoadSchemasErrorsWhenAllSchemaPathGlobsAreEmpty(t *testing.T) {
fsys := afero.NewMemMapFs()
utils.Config.Db.Migrations.SchemaPaths = []string{
filepath.Join(utils.SupabaseDirPath, "schemas", "tables", "*.sql"),
filepath.Join(utils.SupabaseDirPath, "schemas", "views", "*.sql"),
}
t.Cleanup(func() {
utils.Config.Db.Migrations.SchemaPaths = nil
})

schemas, err := loadDeclaredSchemas(fsys)

assert.ErrorContains(t, err, "no files matched pattern")
assert.Empty(t, schemas)
}
39 changes: 38 additions & 1 deletion apps/cli-go/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,46 @@ func (p *RequestPolicy) UnmarshalText(text []byte) error {

type Glob []string

type globOptions struct {
skipEmptyGlobs bool
errorOnAllSkipped bool
}

type GlobOption func(*globOptions)

func WithSkipEmptyGlobs() GlobOption {
return func(o *globOptions) {
o.skipEmptyGlobs = true
}
}

func WithErrorOnAllSkippedGlobs() GlobOption {
return func(o *globOptions) {
o.errorOnAllSkipped = true
}
}

// Match the glob patterns in the given FS to get a deduplicated
// array of all migrations files to apply in the declared order.
func (g Glob) Files(fsys fs.FS) ([]string, error) {
func (g Glob) Files(fsys fs.FS, options ...GlobOption) ([]string, error) {
opts := globOptions{}
for _, apply := range options {
apply(&opts)
}
var result []string
var allErrors []error
var skipped []string
set := make(map[string]struct{})
for _, pattern := range g {
// Glob expects / as path separator on windows
matches, err := fs.Glob(fsys, filepath.ToSlash(pattern))
if err != nil {
allErrors = append(allErrors, errors.Errorf("failed to glob files: %w", err))
} else if len(matches) == 0 {
if opts.skipEmptyGlobs && hasGlobMeta(pattern) {
skipped = append(skipped, pattern)
continue
}
allErrors = append(allErrors, errors.Errorf("no files matched pattern: %s", pattern))
}
sort.Strings(matches)
Expand All @@ -121,9 +149,18 @@ func (g Glob) Files(fsys fs.FS) ([]string, error) {
}
}
}
if opts.errorOnAllSkipped && len(result) == 0 && len(skipped) > 0 {
for _, pattern := range skipped {
allErrors = append(allErrors, errors.Errorf("no files matched pattern: %s", pattern))
}
}
return result, errors.Join(allErrors...)
}

func hasGlobMeta(pattern string) bool {
return strings.ContainsAny(pattern, `*?[`)
}

// We follow these rules when adding new config:
// 1. Update init_config.toml (and init_config.test.toml) with the new key, default value, and comments to explain usage.
// 2. Update config struct with new field and toml tag (spelled in snake_case).
Expand Down
28 changes: 28 additions & 0 deletions apps/cli-go/pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,34 @@ func TestGlobFiles(t *testing.T) {
// Validate files
assert.Empty(t, files)
})

t.Run("skips empty globs when configured", func(t *testing.T) {
fsys := fs.MapFS{
"supabase/schemas/tables/players.sql": &fs.MapFile{},
}
g := Glob{
"supabase/schemas/tables/*.sql",
"supabase/schemas/materialized_views/*.sql",
}

files, err := g.Files(fsys, WithSkipEmptyGlobs())

assert.NoError(t, err)
assert.Equal(t, []string{"supabase/schemas/tables/players.sql"}, files)
})

t.Run("errors when all skipped globs are empty and configured to fail", func(t *testing.T) {
fsys := fs.MapFS{}
g := Glob{
"supabase/schemas/tables/*.sql",
"supabase/schemas/materialized_views/*.sql",
}

files, err := g.Files(fsys, WithSkipEmptyGlobs(), WithErrorOnAllSkippedGlobs())

assert.ErrorContains(t, err, "no files matched pattern")
assert.Empty(t, files)
})
}

func TestLoadFunctionImportMap(t *testing.T) {
Expand Down
Loading