feat(dev): add confirmation to file sync
Add a confirmation gate and --yes bypass to core dev sync so batch file distribution follows the same safety model as the other AI-agent commands.
This commit is contained in:
parent
cbf650918a
commit
c06fd2edfc
3 changed files with 44 additions and 20 deletions
|
|
@ -14,12 +14,12 @@ import (
|
|||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
"dappco.re/go/core/scm/git"
|
||||
"dappco.re/go/core/i18n"
|
||||
coreio "dappco.re/go/core/io"
|
||||
"dappco.re/go/core/log"
|
||||
"dappco.re/go/core/scm/git"
|
||||
"dappco.re/go/core/scm/repos"
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
)
|
||||
|
||||
// File sync command flags
|
||||
|
|
@ -29,6 +29,7 @@ var (
|
|||
fileSyncCoAuthor string
|
||||
fileSyncDryRun bool
|
||||
fileSyncPush bool
|
||||
fileSyncYes bool
|
||||
)
|
||||
|
||||
// AddFileSyncCommand adds the 'sync' command to dev for file syncing.
|
||||
|
|
@ -48,6 +49,7 @@ func AddFileSyncCommand(parent *cli.Command) {
|
|||
syncCmd.Flags().StringVar(&fileSyncCoAuthor, "co-author", "", i18n.T("cmd.dev.file_sync.flag.co_author"))
|
||||
syncCmd.Flags().BoolVar(&fileSyncDryRun, "dry-run", false, i18n.T("cmd.dev.file_sync.flag.dry_run"))
|
||||
syncCmd.Flags().BoolVar(&fileSyncPush, "push", false, i18n.T("cmd.dev.file_sync.flag.push"))
|
||||
syncCmd.Flags().BoolVarP(&fileSyncYes, "yes", "y", false, i18n.T("cmd.dev.file_sync.flag.yes"))
|
||||
|
||||
_ = syncCmd.MarkFlagRequired("to")
|
||||
|
||||
|
|
@ -64,23 +66,6 @@ func runFileSync(source string) error {
|
|||
|
||||
// Validate source exists
|
||||
sourceInfo, err := os.Stat(source) // Keep os.Stat for local source check or use coreio? coreio.Local.IsFile is bool.
|
||||
// If source is local file on disk (not in medium), we can use os.Stat.
|
||||
// But concept is everything is via Medium?
|
||||
// User is running CLI on host. `source` is relative to CWD.
|
||||
// coreio.Local uses absolute path or relative to root (which is "/" by default).
|
||||
// So coreio.Local works.
|
||||
if !coreio.Local.IsFile(source) {
|
||||
// Might be directory
|
||||
// IsFile returns false for directory.
|
||||
}
|
||||
// Let's rely on os.Stat for initial source check to distinguish dir vs file easily if coreio doesn't expose Stat.
|
||||
// coreio doesn't expose Stat.
|
||||
|
||||
// Check using standard os for source determination as we are outside strict sandbox for input args potentially?
|
||||
// But we should use coreio where possible.
|
||||
// coreio.Local.List worked for dirs.
|
||||
// Let's stick to os.Stat for source properties finding as typically allowed for CLI args.
|
||||
|
||||
if err != nil {
|
||||
return log.E("dev.sync", i18n.T("cmd.dev.file_sync.error.source_not_found", map[string]any{"Path": source}), err)
|
||||
}
|
||||
|
|
@ -103,6 +88,16 @@ func runFileSync(source string) error {
|
|||
}
|
||||
cli.Blank()
|
||||
|
||||
if !fileSyncDryRun && !fileSyncYes {
|
||||
cli.Print("%s\n", warningStyle.Render(i18n.T("cmd.dev.file_sync.warning")))
|
||||
cli.Blank()
|
||||
if !cli.Confirm(i18n.T("cmd.dev.file_sync.confirm")) {
|
||||
cli.Text(i18n.T("cli.aborted"))
|
||||
return nil
|
||||
}
|
||||
cli.Blank()
|
||||
}
|
||||
|
||||
var succeeded, skipped, failed int
|
||||
|
||||
for _, repo := range targetRepos {
|
||||
|
|
|
|||
26
cmd/dev/cmd_file_sync_test.go
Normal file
26
cmd/dev/cmd_file_sync_test.go
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
package dev
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"forge.lthn.ai/core/cli/pkg/cli"
|
||||
)
|
||||
|
||||
func TestAddFileSyncCommand_Good(t *testing.T) {
|
||||
root := &cli.Command{Use: "core"}
|
||||
|
||||
AddDevCommands(root)
|
||||
|
||||
syncCmd, _, err := root.Find([]string{"dev", "sync"})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, syncCmd)
|
||||
|
||||
yesFlag := syncCmd.Flags().Lookup("yes")
|
||||
require.NotNil(t, yesFlag)
|
||||
require.Equal(t, "y", yesFlag.Shorthand)
|
||||
|
||||
require.NotNil(t, syncCmd.Flags().Lookup("dry-run"))
|
||||
require.NotNil(t, syncCmd.Flags().Lookup("push"))
|
||||
}
|
||||
|
|
@ -173,6 +173,8 @@
|
|||
"long": "Copy a file or directory to matching repos, optionally committing and pushing the changes.\n\nDesigned for safe file distribution by AI agents.",
|
||||
"source": "Source",
|
||||
"targets": "Targets",
|
||||
"warning": "This will copy files into each target repo.",
|
||||
"confirm": "Sync these repos?",
|
||||
"dry_run_mode": "[dry-run] No changes will be made",
|
||||
"no_changes": "no changes",
|
||||
"summary": "Summary",
|
||||
|
|
@ -181,7 +183,8 @@
|
|||
"message": "Commit message (omit to leave uncommitted)",
|
||||
"push": "Push after committing",
|
||||
"co_author": "Co-author for commits",
|
||||
"dry_run": "Preview without making changes"
|
||||
"dry_run": "Preview without making changes",
|
||||
"yes": "Skip confirmation prompt"
|
||||
},
|
||||
"error": {
|
||||
"source_not_found": "Source not found: {{.Path}}",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue