@@ -9,31 +9,32 @@ import (
99
1010 "rules-cli/internal/auth"
1111 "rules-cli/internal/registry"
12+ "rules-cli/internal/ruleset"
1213
1314 "github.com/fatih/color"
1415 "github.com/spf13/cobra"
1516)
1617
1718var (
18- slug string
1919 visibility string
2020)
2121
2222// publishCmd represents the publish command
2323var publishCmd = & cobra.Command {
24- Use : "publish <rule-file> " ,
24+ Use : "publish [path] " ,
2525 Short : "Publish a rule file to the registry" ,
2626 Long : `Publishes a rule file to the registry.
2727
2828This command requires authentication and will prompt you to login if you're not already.
29- You must specify the organization/ruleset slug to publish to .
29+ The slug is automatically determined from rules.json in the current directory or specified path .
3030
3131The visibility can be set to "public" (default) or "private".
3232
3333Examples:
34- rules publish my-rule.md --slug my-org/my-rules
35- rules publish my-rule.md --slug my-org/my-rules --visibility private` ,
36- Args : cobra .ExactArgs (1 ),
34+ rules publish # Publish from current directory
35+ rules publish ./my-rules # Publish from specified directory
36+ rules publish --visibility private` ,
37+ Args : cobra .MaximumNArgs (1 ),
3738 RunE : runPublishCommand ,
3839}
3940
@@ -45,30 +46,86 @@ func runPublishCommand(cmd *cobra.Command, args []string) error {
4546 return fmt .Errorf ("authentication required to publish rules" )
4647 }
4748
48- // Validate the slug format
49- if slug = = "" {
50- return fmt .Errorf ("--slug is required and must be in the format 'organization/ruleset '" )
49+ // Validate the visibility
50+ if visibility != "public" && visibility ! = "private " {
51+ return fmt .Errorf ("visibility must be either 'public' or 'private '" )
5152 }
5253
53- if ! strings .Contains (slug , "/" ) {
54- return fmt .Errorf ("slug must be in the format 'organization/ruleset'" )
54+ // Determine the path to look for rules.json
55+ var rulesPath string
56+ if len (args ) > 0 {
57+ rulesPath = args [0 ]
5558 }
5659
57- parts := strings .Split (slug , "/" )
58- if len (parts ) != 2 || parts [0 ] == "" || parts [1 ] == "" {
59- return fmt .Errorf ("slug must be in the format 'organization/ruleset'" )
60+ // Load ruleset from the specified path or current directory
61+ rs , err := ruleset .LoadRuleSetFromPath (rulesPath )
62+ if err != nil {
63+ return fmt .Errorf ("failed to load rules.json: %w" , err )
6064 }
6165
62- ownerSlug := parts [0 ]
63- ruleSlug := parts [1 ]
66+ // Validate that the ruleset has a name
67+ if rs .Name == "" {
68+ return fmt .Errorf ("rules.json must have a 'name' field" )
69+ }
6470
65- // Validate the visibility
66- if visibility != "public" && visibility != "private" {
67- return fmt .Errorf ("visibility must be either 'public' or 'private'" )
71+ // Create registry client and get user info
72+ authConfig := auth .LoadAuthConfig ()
73+ client := registry .NewClient (cfg .RegistryURL )
74+ client .SetAuthToken (authConfig .AccessToken )
75+
76+ // Get user information to determine the organization slug
77+ userInfo , err := client .GetUserInfo ()
78+ if err != nil {
79+ return fmt .Errorf ("failed to get user information: %w" , err )
80+ }
81+
82+ // Determine the organization slug
83+ ownerSlug := userInfo .OrgSlug
84+ if ownerSlug == "" {
85+ // Fallback to username if orgSlug is not available
86+ ownerSlug = userInfo .Username
87+ if ownerSlug == "" {
88+ // Fallback to email prefix if username is not available
89+ if userInfo .Email != "" {
90+ ownerSlug = strings .Split (userInfo .Email , "@" )[0 ]
91+ } else {
92+ return fmt .Errorf ("could not determine organization slug from user information" )
93+ }
94+ }
6895 }
6996
70- // Get the rule file path
71- ruleFilePath := args [0 ]
97+ // Use the ruleset name as the rule slug
98+ ruleSlug := rs .Name
99+
100+ // Validate the slug format
101+ if ! isValidSlug (ownerSlug ) || ! isValidSlug (ruleSlug ) {
102+ return fmt .Errorf ("invalid slug format: %s/%s" , ownerSlug , ruleSlug )
103+ }
104+
105+ // Find the main rule file to publish
106+ // Look for index.md in the rules directory
107+ var ruleFilePath string
108+ if rulesPath != "" {
109+ // If a path was specified, look for index.md in that directory
110+ stat , err := os .Stat (rulesPath )
111+ if err == nil && stat .IsDir () {
112+ ruleFilePath = filepath .Join (rulesPath , "index.md" )
113+ }
114+ } else {
115+ // Look in current directory for index.md
116+ ruleFilePath = "index.md"
117+ }
118+
119+ // If index.md doesn't exist, look for any .md file in the rules directory
120+ if _ , err := os .Stat (ruleFilePath ); os .IsNotExist (err ) {
121+ // Look for any .md file in the current directory
122+ files , err := filepath .Glob ("*.md" )
123+ if err == nil && len (files ) > 0 {
124+ ruleFilePath = files [0 ]
125+ } else {
126+ return fmt .Errorf ("no rule file found to publish. Please create an index.md file or specify a rule file" )
127+ }
128+ }
72129
73130 // Check if the file exists
74131 if _ , err := os .Stat (ruleFilePath ); os .IsNotExist (err ) {
@@ -81,11 +138,6 @@ func runPublishCommand(cmd *cobra.Command, args []string) error {
81138 return fmt .Errorf ("failed to read rule file: %w" , err )
82139 }
83140
84- // Create registry client
85- authConfig := auth .LoadAuthConfig ()
86- client := registry .NewClient (cfg .RegistryURL )
87- client .SetAuthToken (authConfig .AccessToken )
88-
89141 // Publish the rule
90142 color .Cyan ("Publishing rule to %s/%s with visibility: %s" , ownerSlug , ruleSlug , visibility )
91143 err = client .PublishRule (ownerSlug , ruleSlug , string (content ), visibility )
@@ -98,13 +150,28 @@ func runPublishCommand(cmd *cobra.Command, args []string) error {
98150 return nil
99151}
100152
153+ // isValidSlug checks if a slug is valid (alphanumeric, hyphens, underscores only)
154+ func isValidSlug (slug string ) bool {
155+ if slug == "" {
156+ return false
157+ }
158+
159+ // Check if slug contains only valid characters
160+ for _ , char := range slug {
161+ if ! ((char >= 'a' && char <= 'z' ) ||
162+ (char >= 'A' && char <= 'Z' ) ||
163+ (char >= '0' && char <= '9' ) ||
164+ char == '-' || char == '_' ) {
165+ return false
166+ }
167+ }
168+
169+ return true
170+ }
171+
101172func init () {
102173 rootCmd .AddCommand (publishCmd )
103174
104175 // Add flags
105- publishCmd .Flags ().StringVar (& slug , "slug" , "" , "The organization/ruleset slug to publish to (required)" )
106176 publishCmd .Flags ().StringVar (& visibility , "visibility" , "public" , "Set the visibility of the rule to 'public' or 'private'" )
107-
108- // Mark required flags
109- publishCmd .MarkFlagRequired ("slug" )
110177}
0 commit comments