Skip to content

Commit 191972b

Browse files
committed
fix
1 parent 4f5ea9b commit 191972b

File tree

6 files changed

+280
-153
lines changed

6 files changed

+280
-153
lines changed

.github/workflows/breaking-changes.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,5 @@ jobs:
5555
- name: Run breaking changes validator
5656
run: |
5757
set -e
58-
go run tools/cmd/breakvalidator/main.go validate < main.json
58+
go run tools/cmd/breakvalidator/main.go generate > changed.json
59+
go run tools/cmd/breakvalidator/main.go validate -m main.json -c changed.json

tools/cmd/breakvalidator/generate.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"encoding/json"
19+
"io"
20+
21+
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/root"
22+
"github.com/spf13/cobra"
23+
"github.com/spf13/pflag"
24+
)
25+
26+
func generateCmd(cmd *cobra.Command) cmdData {
27+
data := cmdData{}
28+
if len(cmd.Aliases) > 0 {
29+
data.Aliases = cmd.Aliases
30+
}
31+
flags := false
32+
data.Flags = map[string]flagData{}
33+
cmd.Flags().VisitAll(func(f *pflag.Flag) {
34+
data.Flags[f.Name] = flagData{
35+
Type: f.Value.Type(),
36+
Default: f.DefValue,
37+
Short: f.Shorthand,
38+
}
39+
flags = true
40+
})
41+
if !flags {
42+
data.Flags = nil
43+
}
44+
return data
45+
}
46+
47+
func generateCmds(cmd *cobra.Command) map[string]cmdData {
48+
data := map[string]cmdData{}
49+
data[cmd.CommandPath()] = generateCmd(cmd)
50+
for _, c := range cmd.Commands() {
51+
for k, v := range generateCmds(c) {
52+
data[k] = v
53+
}
54+
}
55+
return data
56+
}
57+
58+
func generateCmdRun(output io.Writer) error {
59+
cliCmd := root.Builder()
60+
data := generateCmds(cliCmd)
61+
return json.NewEncoder(output).Encode(data)
62+
}
63+
64+
func buildGenerateCmd() *cobra.Command {
65+
generateCmd := &cobra.Command{
66+
Use: "generate",
67+
Short: "Generate the CLI command structure.",
68+
RunE: func(cmd *cobra.Command, _ []string) error {
69+
return generateCmdRun(cmd.OutOrStdout())
70+
},
71+
}
72+
return generateCmd
73+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2025 MongoDB Inc
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package main
16+
17+
import (
18+
"reflect"
19+
"testing"
20+
21+
"github.com/spf13/cobra"
22+
)
23+
24+
func TestGenerateCmds(t *testing.T) {
25+
cliCmd := &cobra.Command{
26+
Use: "test",
27+
Aliases: []string{"testa"},
28+
}
29+
cliCmd.Flags().StringP("flag1", "f", "default1", "flag1")
30+
generatedData := generateCmds(cliCmd)
31+
32+
expectedData := map[string]cmdData{
33+
"test": {
34+
Aliases: []string{"testa"},
35+
Flags: map[string]flagData{
36+
"flag1": {
37+
Type: "string",
38+
Default: "default1",
39+
Short: "f",
40+
},
41+
},
42+
},
43+
}
44+
45+
if !reflect.DeepEqual(generatedData, expectedData) {
46+
t.Fatalf("got: %v, expected: %v", generatedData, expectedData)
47+
}
48+
}

tools/cmd/breakvalidator/main.go

Lines changed: 4 additions & 152 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,9 @@
1515
package main
1616

1717
import (
18-
"encoding/json"
19-
"errors"
20-
"fmt"
21-
"io"
2218
"os"
23-
"slices"
2419

25-
"github.com/mongodb/mongodb-atlas-cli/atlascli/internal/cli/root"
2620
"github.com/spf13/cobra"
27-
"github.com/spf13/pflag"
28-
)
29-
30-
var (
31-
errFlagDeleted = errors.New("flag was deleted")
32-
errFlagTypeChanged = errors.New("flag type changed")
33-
errFlagDefaultChanged = errors.New("flag default value changed")
34-
errCmdDeleted = errors.New("command deleted")
35-
errCmdRemovedAlias = errors.New("command alias removed")
36-
errFlagShortChanged = errors.New("flag shorthand changed")
3721
)
3822

3923
type flagData struct {
@@ -47,151 +31,19 @@ type cmdData struct {
4731
Flags map[string]flagData `json:"flags"`
4832
}
4933

50-
func generateCmd(cmd *cobra.Command) cmdData {
51-
data := cmdData{}
52-
if len(cmd.Aliases) > 0 {
53-
data.Aliases = cmd.Aliases
54-
}
55-
flags := false
56-
data.Flags = map[string]flagData{}
57-
cmd.Flags().VisitAll(func(f *pflag.Flag) {
58-
data.Flags[f.Name] = flagData{
59-
Type: f.Value.Type(),
60-
Default: f.DefValue,
61-
Short: f.Shorthand,
62-
}
63-
flags = true
64-
})
65-
if !flags {
66-
data.Flags = nil
67-
}
68-
return data
69-
}
70-
71-
func generateCmds(cmd *cobra.Command) map[string]cmdData {
72-
data := map[string]cmdData{}
73-
data[cmd.CommandPath()] = generateCmd(cmd)
74-
for _, c := range cmd.Commands() {
75-
for k, v := range generateCmds(c) {
76-
data[k] = v
77-
}
78-
}
79-
return data
80-
}
81-
82-
func generateCmdRun(output io.Writer) error {
83-
cliCmd := root.Builder()
84-
data := generateCmds(cliCmd)
85-
return json.NewEncoder(output).Encode(data)
86-
}
87-
88-
func compareFlags(cmdPath string, mainFlags, changedFlags map[string]flagData) []error {
89-
if mainFlags == nil {
90-
return nil
91-
}
92-
93-
changes := []error{}
94-
95-
for flagName, flagValue := range mainFlags {
96-
changedFlagValue, ok := changedFlags[flagName]
97-
if !ok {
98-
changes = append(changes, fmt.Errorf("%w: %s --%s", errFlagDeleted, cmdPath, flagName))
99-
continue
100-
}
101-
102-
if flagValue.Type != changedFlagValue.Type {
103-
changes = append(changes, fmt.Errorf("%w: %s --%s", errFlagTypeChanged, cmdPath, flagName))
104-
}
105-
106-
if flagValue.Default != changedFlagValue.Default {
107-
changes = append(changes, fmt.Errorf("%w: %s --%s", errFlagDefaultChanged, cmdPath, flagName))
108-
}
109-
110-
if flagValue.Short != changedFlagValue.Short {
111-
changes = append(changes, fmt.Errorf("%w: %s --%s", errFlagShortChanged, cmdPath, flagName))
112-
}
113-
}
114-
115-
return changes
116-
}
117-
118-
func compareCmds(changedData, mainData map[string]cmdData) error {
119-
changes := []error{}
120-
for cmdPath, mv := range mainData {
121-
cv, ok := changedData[cmdPath]
122-
if !ok {
123-
changes = append(changes, fmt.Errorf("%w: %s", errCmdDeleted, cmdPath))
124-
continue
125-
}
126-
127-
if mv.Aliases != nil {
128-
mainAliases := mv.Aliases
129-
changedAliases := cv.Aliases
130-
131-
for _, alias := range mainAliases {
132-
if !slices.Contains(changedAliases, alias) {
133-
changes = append(changes, fmt.Errorf("%w: %s", errCmdRemovedAlias, cmdPath))
134-
}
135-
}
136-
}
137-
138-
changes = append(changes, compareFlags(cmdPath, mv.Flags, cv.Flags)...)
139-
}
140-
141-
if len(changes) > 0 {
142-
return errors.Join(changes...)
143-
}
144-
145-
return nil
146-
}
147-
148-
func validateCmdRun(output io.Writer, input io.Reader) error {
149-
var inputData map[string]cmdData
150-
if err := json.NewDecoder(input).Decode(&inputData); err != nil {
151-
return err
152-
}
153-
154-
cliCmd := root.Builder()
155-
generatedData := generateCmds(cliCmd)
156-
157-
err := compareCmds(generatedData, inputData)
158-
if err != nil {
159-
return err
160-
}
161-
162-
fmt.Fprintln(output, "no breaking changes detected")
163-
return nil
164-
}
165-
166-
func buildCmd() *cobra.Command {
167-
generateCmd := &cobra.Command{
168-
Use: "generate",
169-
Short: "Generate the CLI command structure.",
170-
RunE: func(cmd *cobra.Command, _ []string) error {
171-
return generateCmdRun(cmd.OutOrStdout())
172-
},
173-
}
174-
175-
validateCmd := &cobra.Command{
176-
Use: "validate",
177-
Short: "Validate the CLI command structure.",
178-
RunE: func(cmd *cobra.Command, _ []string) error {
179-
return validateCmdRun(cmd.OutOrStdout(), cmd.InOrStdin())
180-
},
181-
}
182-
34+
func buildRootCmd() *cobra.Command {
18335
rootCmd := &cobra.Command{
18436
Use: "breakvalidator",
18537
Short: "CLI tool to validate breaking changes in the CLI.",
18638
}
187-
rootCmd.AddCommand(generateCmd)
188-
rootCmd.AddCommand(validateCmd)
39+
rootCmd.AddCommand(buildGenerateCmd())
40+
rootCmd.AddCommand(buildValidateCmd())
18941

19042
return rootCmd
19143
}
19244

19345
func main() {
194-
rootCmd := buildCmd()
46+
rootCmd := buildRootCmd()
19547
err := rootCmd.Execute()
19648
if err != nil {
19749
os.Exit(1)

0 commit comments

Comments
 (0)