feat(dev): support glob targets in apply command

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 09:02:04 +00:00
parent ae3935919e
commit 93c8eef876
5 changed files with 77 additions and 28 deletions

View file

@ -12,14 +12,14 @@ import (
"os"
"os/exec"
"path/filepath"
"strings"
"sort"
"forge.lthn.ai/core/cli/pkg/cli"
core "dappco.re/go/core/log"
"dappco.re/go/core/scm/git"
"dappco.re/go/core/i18n"
"dappco.re/go/core/io"
core "dappco.re/go/core/log"
"dappco.re/go/core/scm/git"
"dappco.re/go/core/scm/repos"
"forge.lthn.ai/core/cli/pkg/cli"
)
// Apply command flags
@ -235,29 +235,39 @@ func getApplyTargetRepos() ([]*repos.Repo, error) {
return nil, core.E("dev.apply", "failed to load registry", err)
}
// If --repos specified, filter to those
if applyRepos != "" {
repoNames := strings.Split(applyRepos, ",")
nameSet := make(map[string]bool)
return filterTargetRepos(registry, applyRepos), nil
}
// filterTargetRepos selects repos by exact name/path or glob pattern.
func filterTargetRepos(registry *repos.Registry, selection string) []*repos.Repo {
repoNames := make([]string, 0, len(registry.Repos))
for name := range registry.Repos {
repoNames = append(repoNames, name)
}
sort.Strings(repoNames)
if selection == "" {
matched := make([]*repos.Repo, 0, len(repoNames))
for _, name := range repoNames {
nameSet[strings.TrimSpace(name)] = true
matched = append(matched, registry.Repos[name])
}
return matched
}
patterns := splitPatterns(selection)
var matched []*repos.Repo
for _, repo := range registry.Repos {
if nameSet[repo.Name] {
for _, name := range repoNames {
repo := registry.Repos[name]
for _, candidate := range patterns {
if matchGlob(repo.Name, candidate) || matchGlob(repo.Path, candidate) {
matched = append(matched, repo)
break
}
}
return matched, nil
}
// Return all repos as slice
var all []*repos.Repo
for _, repo := range registry.Repos {
all = append(all, repo)
}
return all, nil
return matched
}
// runCommandInRepo runs a shell command in a repo directory

39
cmd/dev/cmd_apply_test.go Normal file
View file

@ -0,0 +1,39 @@
package dev
import (
"testing"
"github.com/stretchr/testify/require"
"dappco.re/go/core/scm/repos"
)
func TestFilterTargetRepos_Good(t *testing.T) {
registry := &repos.Registry{
Repos: map[string]*repos.Repo{
"core-api": &repos.Repo{Name: "core-api", Path: "packages/core-api"},
"core-web": &repos.Repo{Name: "core-web", Path: "packages/core-web"},
"docs-site": &repos.Repo{Name: "docs-site", Path: "sites/docs"},
},
}
t.Run("exact names", func(t *testing.T) {
matched := filterTargetRepos(registry, "core-api,docs-site")
require.Len(t, matched, 2)
require.Equal(t, "core-api", matched[0].Name)
require.Equal(t, "docs-site", matched[1].Name)
})
t.Run("glob patterns", func(t *testing.T) {
matched := filterTargetRepos(registry, "core-*,sites/*")
require.Len(t, matched, 3)
require.Equal(t, "core-api", matched[0].Name)
require.Equal(t, "core-web", matched[1].Name)
require.Equal(t, "docs-site", matched[2].Name)
})
t.Run("all repos when empty", func(t *testing.T) {
matched := filterTargetRepos(registry, "")
require.Len(t, matched, 3)
})
}

View file

@ -59,6 +59,6 @@ func TestOutputPowershellInstall_Good(t *testing.T) {
return outputPowershellInstall(DefaultCIConfig(), "dev")
})
require.NoError(t, err)
require.Contains(t, out, `scoop bucket add host-uk https://forge.lthn.ai/core/scoop-bucket.git`)
require.Contains(t, out, `scoop bucket add host-uk $ScoopBucket`)
require.NotContains(t, out, `https://https://forge.lthn.ai/core/scoop-bucket.git`)
}

View file

@ -94,9 +94,9 @@ func runPackageWizard(reg *repos.Registry, preselectedTypes []string) ([]string,
return selected, nil
}
func filterReposByTypes(repos []*repos.Repo, allowedTypes []string) []*repos.Repo {
func filterReposByTypes(repoList []*repos.Repo, allowedTypes []string) []*repos.Repo {
if len(allowedTypes) == 0 {
return repos
return repoList
}
allowed := make(map[string]struct{}, len(allowedTypes))
@ -108,11 +108,11 @@ func filterReposByTypes(repos []*repos.Repo, allowedTypes []string) []*repos.Rep
}
if len(allowed) == 0 {
return repos
return repoList
}
filtered := make([]*repos.Repo, 0, len(repos))
for _, repo := range repos {
filtered := make([]*repos.Repo, 0, len(repoList))
for _, repo := range repoList {
if _, ok := allowed[repo.Type]; ok {
filtered = append(filtered, repo)
}

View file

@ -151,7 +151,7 @@
"flag": {
"command": "Shell command to run in each repo",
"script": "Script file to run in each repo",
"repos": "Comma-separated list of repo names to target",
"repos": "Comma-separated list of repo names, paths, or glob patterns to target",
"commit": "Commit changes after running",
"message": "Commit message (required with --commit)",
"push": "Push after committing",