Skip to content

Commit 5d3ec01

Browse files
committed
[minor] add a drupal registry feature to interact with entities
1 parent 36c4459 commit 5d3ec01

23 files changed

+1976
-1
lines changed

cmd/node.go

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"os"
8+
9+
"github.com/libops/sitectl-drupal/drupal"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
// clientKey is the context key for the Drupal client
14+
type clientKey struct{}
15+
16+
var bundleConfig string
17+
18+
// nodeCmd represents the node command group
19+
var nodeCmd = &cobra.Command{
20+
Use: "node",
21+
Short: "Drupal node operations",
22+
Long: `Commands for interacting with Drupal nodes via the JSON API.
23+
24+
Use --bundle-config to load bundle definitions from a Drupal config sync export
25+
or generated YAML file. This enables field validation and type information.`,
26+
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
27+
var opts []drupal.ClientOption
28+
29+
// Load bundle config if provided
30+
if bundleConfig != "" {
31+
opts = append(opts, drupal.WithBundlesFromPath(bundleConfig))
32+
}
33+
34+
// Load auth from environment
35+
opts = append(opts, drupal.WithBasicAuthFromEnv("DRUPAL_USERNAME", "DRUPAL_PASSWORD"))
36+
37+
client := drupal.NewClient(opts...)
38+
39+
// Store client in context for subcommands
40+
ctx := context.WithValue(cmd.Context(), clientKey{}, client)
41+
cmd.SetContext(ctx)
42+
43+
return nil
44+
},
45+
}
46+
47+
func init() {
48+
// Register persistent flag for bundle configuration
49+
nodeCmd.PersistentFlags().StringVar(&bundleConfig, "bundle-config", "",
50+
"Path to bundle configuration YAML (from Drupal config sync or generated)")
51+
52+
// Add subcommands
53+
nodeCmd.AddCommand(nodeFetchCmd)
54+
nodeCmd.AddCommand(nodeValidateCmd)
55+
nodeCmd.AddCommand(nodeBundlesCmd)
56+
nodeCmd.AddCommand(nodeCacheClearCmd)
57+
}
58+
59+
// getClient retrieves the Drupal client from the command context
60+
func getClient(cmd *cobra.Command) *drupal.Client {
61+
return cmd.Context().Value(clientKey{}).(*drupal.Client)
62+
}
63+
64+
// nodeFetchCmd fetches and displays a node
65+
var nodeFetchCmd = &cobra.Command{
66+
Use: "fetch <url>",
67+
Short: "Fetch a node from the Drupal JSON API",
68+
Long: `Fetches a node from the Drupal JSON API and displays it as JSON.
69+
70+
The URL should be a full URL to the node JSON endpoint, e.g.:
71+
https://example.com/node/123?_format=json
72+
73+
If --bundle-config is provided, the node will be validated against its bundle schema.`,
74+
Args: cobra.ExactArgs(1),
75+
RunE: func(cmd *cobra.Command, args []string) error {
76+
url := args[0]
77+
client := getClient(cmd)
78+
79+
node, err := client.FetchNode(url)
80+
if err != nil {
81+
return fmt.Errorf("fetching node: %w", err)
82+
}
83+
84+
// Validate if registry has bundles loaded
85+
if errors := node.Validate(); len(errors) > 0 {
86+
fmt.Fprintf(os.Stderr, "Warning: validation errors for bundle %q:\n", node.Bundle())
87+
for _, e := range errors {
88+
fmt.Fprintf(os.Stderr, " - %s\n", e)
89+
}
90+
}
91+
92+
// Output as JSON
93+
enc := json.NewEncoder(os.Stdout)
94+
enc.SetIndent("", " ")
95+
return enc.Encode(node)
96+
},
97+
}
98+
99+
// nodeValidateCmd validates a node against bundle schema
100+
var nodeValidateCmd = &cobra.Command{
101+
Use: "validate <url>",
102+
Short: "Validate a node against its bundle schema",
103+
Long: `Fetches a node and validates it against the bundle schema.
104+
105+
Requires --bundle-config to be set with bundle definitions.`,
106+
Args: cobra.ExactArgs(1),
107+
RunE: func(cmd *cobra.Command, args []string) error {
108+
url := args[0]
109+
client := getClient(cmd)
110+
111+
node, err := client.FetchNode(url)
112+
if err != nil {
113+
return fmt.Errorf("fetching node: %w", err)
114+
}
115+
116+
registry := node.Registry()
117+
if registry == nil {
118+
return fmt.Errorf("no bundle definitions loaded - use --bundle-config")
119+
}
120+
121+
def, ok := registry.GetBundle(node.Bundle())
122+
if !ok {
123+
return fmt.Errorf("unknown bundle %q - not in registry", node.Bundle())
124+
}
125+
126+
fmt.Printf("Validating node (nid: %s) against bundle: %s\n", node.Nid.String(), def.Name)
127+
if def.Description != "" {
128+
fmt.Printf("Bundle description: %s\n", def.Description)
129+
}
130+
fmt.Println()
131+
132+
errors := node.Validate()
133+
if len(errors) == 0 {
134+
fmt.Println("Node is valid")
135+
return nil
136+
}
137+
138+
fmt.Println("Validation errors:")
139+
for _, e := range errors {
140+
fmt.Printf(" - %s\n", e)
141+
}
142+
return fmt.Errorf("validation failed with %d error(s)", len(errors))
143+
},
144+
}
145+
146+
// nodeBundlesCmd lists registered bundles
147+
var nodeBundlesCmd = &cobra.Command{
148+
Use: "bundles",
149+
Short: "List all registered bundle definitions",
150+
Long: `Lists all bundle definitions loaded from --bundle-config.
151+
152+
Shows bundle names, descriptions, field counts, and required fields.`,
153+
RunE: func(cmd *cobra.Command, args []string) error {
154+
client := getClient(cmd)
155+
156+
bundles := client.Registry.ListBundles()
157+
if len(bundles) == 0 {
158+
fmt.Println("No bundles registered")
159+
fmt.Println("Use --bundle-config to load bundle definitions")
160+
return nil
161+
}
162+
163+
fmt.Println("Registered bundles:")
164+
for _, name := range bundles {
165+
def, _ := client.Registry.GetBundle(name)
166+
fmt.Printf("\n %s (%s)\n", def.Name, def.MachineName)
167+
if def.Description != "" {
168+
fmt.Printf(" %s\n", def.Description)
169+
}
170+
fmt.Printf(" Fields: %d\n", len(def.Fields))
171+
172+
// List required fields
173+
var required []string
174+
for _, f := range def.Fields {
175+
if f.Required {
176+
required = append(required, f.Name)
177+
}
178+
}
179+
if len(required) > 0 {
180+
fmt.Printf(" Required: %v\n", required)
181+
}
182+
}
183+
184+
return nil
185+
},
186+
}
187+
188+
// nodeCacheClearCmd clears the bundle definition cache
189+
var nodeCacheClearCmd = &cobra.Command{
190+
Use: "cache-clear",
191+
Short: "Clear cached bundle definitions",
192+
Long: `Clears the bundle definition cache from ~/.sitectl/cache`,
193+
RunE: func(cmd *cobra.Command, args []string) error {
194+
if err := drupal.ClearCache(); err != nil {
195+
return fmt.Errorf("clearing cache: %w", err)
196+
}
197+
fmt.Println("Bundle definition cache cleared")
198+
return nil
199+
},
200+
}

cmd/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ func RegisterCommands(s *plugin.SDK) {
2121
sdk.AddCommand(backupCmd)
2222
sdk.AddCommand(drushCmd)
2323
sdk.AddCommand(loginCmd)
24+
sdk.AddCommand(nodeCmd)
2425
}

0 commit comments

Comments
 (0)