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
58 changes: 58 additions & 0 deletions .surface-breaking
Original file line number Diff line number Diff line change
Expand Up @@ -1513,3 +1513,61 @@ ARG basecamp folders move 00 <id|url>
ARG basecamp vault move 00 <id|url>
ARG basecamp vaults move 00 <id|url>
ARG basecamp tools move 00 <id>
FLAG basecamp cards column copy --card-table type=string
FLAG basecamp cards copy --card-table type=string
FLAG basecamp checkin question copy --questionnaire type=string
FLAG basecamp checkin question move --questionnaire type=string
FLAG basecamp checkins question copy --questionnaire type=string
FLAG basecamp checkins question move --questionnaire type=string
FLAG basecamp docs copy --folder type=string
FLAG basecamp docs copy --vault type=string
FLAG basecamp docs move --folder type=string
FLAG basecamp docs move --vault type=string
FLAG basecamp documents copy --folder type=string
FLAG basecamp documents copy --vault type=string
FLAG basecamp documents move --folder type=string
FLAG basecamp documents move --vault type=string
FLAG basecamp file copy --folder type=string
FLAG basecamp file copy --vault type=string
FLAG basecamp file move --folder type=string
FLAG basecamp file move --vault type=string
FLAG basecamp files copy --folder type=string
FLAG basecamp files copy --vault type=string
FLAG basecamp files move --folder type=string
FLAG basecamp files move --vault type=string
FLAG basecamp folders copy --folder type=string
FLAG basecamp folders copy --vault type=string
FLAG basecamp folders move --folder type=string
FLAG basecamp folders move --vault type=string
FLAG basecamp forwards copy --inbox type=string
FLAG basecamp forwards move --inbox type=string
FLAG basecamp messages copy --message-board type=string
FLAG basecamp messages move --message-board type=string
FLAG basecamp msgs copy --message-board type=string
FLAG basecamp msgs move --message-board type=string
FLAG basecamp schedule copy --schedule type=string
FLAG basecamp schedule move --schedule type=string
FLAG basecamp tlgroup copy --list type=string
FLAG basecamp tlgroup move --list type=string
FLAG basecamp tlgroup position --list type=string
FLAG basecamp tlgroups copy --list type=string
FLAG basecamp tlgroups move --list type=string
FLAG basecamp tlgroups position --list type=string
FLAG basecamp todolistgroup copy --list type=string
FLAG basecamp todolistgroup move --list type=string
FLAG basecamp todolistgroup position --list type=string
FLAG basecamp todolistgroups copy --list type=string
FLAG basecamp todolistgroups move --list type=string
FLAG basecamp todolistgroups position --list type=string
FLAG basecamp uploads copy --folder type=string
FLAG basecamp uploads copy --vault type=string
FLAG basecamp uploads move --folder type=string
FLAG basecamp uploads move --vault type=string
FLAG basecamp vault copy --folder type=string
FLAG basecamp vault copy --vault type=string
FLAG basecamp vault move --folder type=string
FLAG basecamp vault move --vault type=string
FLAG basecamp vaults copy --folder type=string
FLAG basecamp vaults copy --vault type=string
FLAG basecamp vaults move --folder type=string
FLAG basecamp vaults move --vault type=string
3 changes: 3 additions & 0 deletions internal/cli/help.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ func parentScopedFlags(cmd *cobra.Command) *pflag.FlagSet {
if rootFlag != nil && rootFlag == f {
return // root-level — stays in INHERITED FLAGS
}
if commands.FilingIgnores(cmd, f) {
return
}
ps.AddFlag(f)
})
return ps
Expand Down
16 changes: 12 additions & 4 deletions internal/commands/filing.go
Original file line number Diff line number Diff line change
Expand Up @@ -652,18 +652,18 @@ func guardFilingFlags(cmd *cobra.Command, reads ...string) *cobra.Command {
cmd.Annotations[AnnotationFilingReads] = strings.Join(reads, " ")
run := cmd.RunE
cmd.RunE = func(cmd *cobra.Command, args []string) error {
if err := refuseUnreadContainerFlags(cmd, reads); err != nil {
if err := refuseUnreadContainerFlags(cmd); err != nil {
return err
}
return run(cmd, args)
}
return cmd
}

func refuseUnreadContainerFlags(cmd *cobra.Command, reads []string) error {
func refuseUnreadContainerFlags(cmd *cobra.Command) error {
var unread *pflag.Flag
cmd.InheritedFlags().VisitAll(func(f *pflag.Flag) {
if unread == nil && f.Changed && isContainerFlag(cmd, f) && !slices.Contains(reads, f.Name) {
if unread == nil && f.Changed && FilingIgnores(cmd, f) {
unread = f
}
})
Expand All @@ -672,11 +672,19 @@ func refuseUnreadContainerFlags(cmd *cobra.Command, reads []string) error {
}
hint := fmt.Sprintf("%s takes no destination; drop --%s", cmd.CommandPath(), unread.Name)
if dests := destinationFlags(cmd); len(dests) > 0 {
hint = fmt.Sprintf("Name where it goes with %s (see %s --help)", strings.Join(dests, " or "), cmd.CommandPath())
hint = fmt.Sprintf("Drop --%s; to send it somewhere else, name where it goes with %s (see %s --help)", unread.Name, strings.Join(dests, " or "), cmd.CommandPath())
}
return output.ErrUsageHint(fmt.Sprintf("%s does not read --%s, so it would ignore it", cmd.CommandPath(), unread.Name), hint)
}

// FilingIgnores reports whether cmd moves or copies recordings and refuses the
// inherited flag f as a container flag it never reads. Help leaves such flags
// out, so it never offers one the command would refuse.
func FilingIgnores(cmd *cobra.Command, f *pflag.Flag) bool {
reads, guarded := cmd.Annotations[AnnotationFilingReads]
return guarded && isContainerFlag(cmd, f) && !slices.Contains(strings.Fields(reads), f.Name)
}

func isContainerFlag(cmd *cobra.Command, f *pflag.Flag) bool {
switch f.Name {
case "project", "in":
Expand Down
27 changes: 27 additions & 0 deletions internal/commands/filing_guard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"context"
"io"
"net/http"
"regexp"
"slices"
"sort"
"strings"
Expand Down Expand Up @@ -117,6 +118,32 @@ func TestFilingCommandsRefuseContainerFlagsTheyDoNotRead(t *testing.T) {
assert.Positive(t, rows)
}

// Help is how people and agents pick flags, so it must not offer one the
// command refuses: messages copy --help said --message-board was "required if
// project has multiple", and every copy that followed it failed.
func TestFilingCommandHelpOffersOnlyTheContainerFlagsItReads(t *testing.T) {
t.Setenv("BASECAMP_NO_KEYRING", "1")
hidden := 0
for path, reads := range filingReads {
var buf bytes.Buffer
root := buildRootWithAllCommands()
root.SetOut(&buf)
root.SetArgs(append(strings.Fields(path)[1:], "--help"))
require.NoError(t, root.Execute())
help := buf.String()
for _, flag := range inheritedContainerFlags(t, path) {
offered := regexp.MustCompile(`(?m)^\s+(-\w, )?--` + regexp.QuoteMeta(flag) + `\s`).MatchString(help)
if slices.Contains(reads, flag) {
assert.True(t, offered, "%s --help leaves out --%s, which it reads", path, flag)
continue
}
hidden++
assert.False(t, offered, "%s --help offers --%s, which it refuses", path, flag)
}
}
assert.Positive(t, hidden)
}

func TestTodosCopyRefusesTheTodolistFlagItWouldIgnore(t *testing.T) {
for name, args := range map[string][]string{
"after the command": {"todos", "copy", "456", "--todolist", "901"},
Expand Down
Loading