Skip to content

Commit b67c43d

Browse files
Copilotpelikhan
andcommitted
Add VSCode YAML schema validation setup to init command
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent d0ac1ae commit b67c43d

7 files changed

Lines changed: 281 additions & 15 deletions

File tree

‎.github/workflows/agent-performance-analyzer.lock.yml‎

Lines changed: 3 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎.github/workflows/campaign-manager.lock.yml‎

Lines changed: 3 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎.github/workflows/workflow-health-manager.lock.yml‎

Lines changed: 3 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎pkg/cli/init.go‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,36 @@ func InitRepository(verbose bool, mcp bool, campaign bool, tokens bool, engine s
128128
}
129129
}
130130

131+
// Configure VSCode settings for YAML schema validation
132+
initLog.Print("Configuring VSCode YAML schema validation")
133+
134+
// Write workflow schema to .github/aw/
135+
if err := ensureWorkflowSchema(verbose); err != nil {
136+
initLog.Printf("Failed to write workflow schema: %v", err)
137+
return fmt.Errorf("failed to write workflow schema: %w", err)
138+
}
139+
if verbose {
140+
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Created .github/aw/main_workflow_schema.json"))
141+
}
142+
143+
// Update .vscode/settings.json
144+
if err := ensureVSCodeSettings(verbose); err != nil {
145+
initLog.Printf("Failed to update VSCode settings: %v", err)
146+
return fmt.Errorf("failed to update VSCode settings: %w", err)
147+
}
148+
if verbose {
149+
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Updated .vscode/settings.json"))
150+
}
151+
152+
// Update .vscode/extensions.json
153+
if err := ensureVSCodeExtensions(verbose); err != nil {
154+
initLog.Printf("Failed to update VSCode extensions: %v", err)
155+
return fmt.Errorf("failed to update VSCode extensions: %w", err)
156+
}
157+
if verbose {
158+
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Updated .vscode/extensions.json"))
159+
}
160+
131161
// Validate tokens if requested
132162
if tokens {
133163
initLog.Print("Validating repository secrets for agentic workflows")
@@ -148,6 +178,8 @@ func InitRepository(verbose bool, mcp bool, campaign bool, tokens bool, engine s
148178
fmt.Fprintln(os.Stderr, "")
149179
fmt.Fprintln(os.Stderr, console.FormatSuccessMessage("Repository initialized for agentic workflows!"))
150180
fmt.Fprintln(os.Stderr, "")
181+
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("VSCode YAML schema validation configured"))
182+
fmt.Fprintln(os.Stderr, "")
151183
if mcp {
152184
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("GitHub Copilot Agent MCP integration configured"))
153185
fmt.Fprintln(os.Stderr, "")

‎pkg/cli/init_command.go‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ This command:
2424
- Creates the agent for workflow creation at .github/agents/create-agentic-workflow.agent.md
2525
- Creates the debug agentic workflow agent at .github/agents/debug-agentic-workflow.agent.md
2626
- Removes old prompt files from .github/prompts/ if they exist
27+
- Configures VSCode YAML schema validation (.github/aw/main_workflow_schema.json, .vscode/settings.json, .vscode/extensions.json)
2728
2829
By default (without --no-mcp):
2930
- Creates .github/workflows/copilot-setup-steps.yml with gh-aw installation steps

‎pkg/cli/vscode_config.go‎

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
package cli
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
"path/filepath"
8+
9+
"github.com/githubnext/gh-aw/pkg/logger"
10+
"github.com/githubnext/gh-aw/pkg/parser"
11+
)
12+
13+
var vscodeConfigLog = logger.New("cli:vscode_config")
14+
15+
// VSCodeSettings represents the structure of .vscode/settings.json
16+
type VSCodeSettings struct {
17+
YAMLSchemas map[string]any `json:"yaml.schemas,omitempty"`
18+
// Include other commonly used settings as any to preserve them
19+
Other map[string]any `json:"-"`
20+
}
21+
22+
// VSCodeExtensions represents the structure of .vscode/extensions.json
23+
type VSCodeExtensions struct {
24+
Recommendations []string `json:"recommendations"`
25+
}
26+
27+
// UnmarshalJSON custom unmarshaler for VSCodeSettings to preserve unknown fields
28+
func (s *VSCodeSettings) UnmarshalJSON(data []byte) error {
29+
// First unmarshal into a generic map to capture all fields
30+
var raw map[string]any
31+
if err := json.Unmarshal(data, &raw); err != nil {
32+
return err
33+
}
34+
35+
// Extract yaml.schemas if it exists
36+
if yamlSchemas, ok := raw["yaml.schemas"]; ok {
37+
if schemas, ok := yamlSchemas.(map[string]any); ok {
38+
s.YAMLSchemas = schemas
39+
}
40+
delete(raw, "yaml.schemas")
41+
}
42+
43+
// Initialize Other if nil and store remaining fields
44+
if s.Other == nil {
45+
s.Other = make(map[string]any)
46+
}
47+
for k, v := range raw {
48+
s.Other[k] = v
49+
}
50+
51+
return nil
52+
}
53+
54+
// MarshalJSON custom marshaler for VSCodeSettings to include all fields
55+
func (s VSCodeSettings) MarshalJSON() ([]byte, error) {
56+
// Create a map with all fields
57+
result := make(map[string]any)
58+
59+
// Add all other fields first
60+
for k, v := range s.Other {
61+
result[k] = v
62+
}
63+
64+
// Add yaml.schemas if present
65+
if len(s.YAMLSchemas) > 0 {
66+
result["yaml.schemas"] = s.YAMLSchemas
67+
}
68+
69+
return json.Marshal(result)
70+
}
71+
72+
// ensureWorkflowSchema writes the main workflow schema to .github/aw/main_workflow_schema.json
73+
func ensureWorkflowSchema(verbose bool) error {
74+
vscodeConfigLog.Print("Writing main workflow schema to .github/aw/")
75+
76+
// Create .github/aw directory if it doesn't exist
77+
awDir := filepath.Join(".github", "aw")
78+
if err := os.MkdirAll(awDir, 0755); err != nil {
79+
return fmt.Errorf("failed to create .github/aw directory: %w", err)
80+
}
81+
vscodeConfigLog.Printf("Ensured directory exists: %s", awDir)
82+
83+
schemaPath := filepath.Join(awDir, "main_workflow_schema.json")
84+
85+
// Get the embedded schema from parser package
86+
schemaContent := parser.GetMainWorkflowSchema()
87+
88+
// Write schema file
89+
if err := os.WriteFile(schemaPath, []byte(schemaContent), 0644); err != nil {
90+
return fmt.Errorf("failed to write workflow schema: %w", err)
91+
}
92+
vscodeConfigLog.Printf("Wrote schema to: %s", schemaPath)
93+
94+
if verbose {
95+
fmt.Fprintf(os.Stderr, "Created workflow schema at %s\n", schemaPath)
96+
}
97+
98+
return nil
99+
}
100+
101+
// ensureVSCodeSettings creates or updates .vscode/settings.json with YAML schema configuration
102+
func ensureVSCodeSettings(verbose bool) error {
103+
vscodeConfigLog.Print("Creating or updating .vscode/settings.json")
104+
105+
// Create .vscode directory if it doesn't exist
106+
vscodeDir := ".vscode"
107+
if err := os.MkdirAll(vscodeDir, 0755); err != nil {
108+
return fmt.Errorf("failed to create .vscode directory: %w", err)
109+
}
110+
vscodeConfigLog.Printf("Ensured directory exists: %s", vscodeDir)
111+
112+
settingsPath := filepath.Join(vscodeDir, "settings.json")
113+
114+
// Read existing settings if they exist
115+
var settings VSCodeSettings
116+
if data, err := os.ReadFile(settingsPath); err == nil {
117+
vscodeConfigLog.Printf("Reading existing settings from: %s", settingsPath)
118+
if err := json.Unmarshal(data, &settings); err != nil {
119+
return fmt.Errorf("failed to parse existing settings.json: %w", err)
120+
}
121+
} else {
122+
vscodeConfigLog.Print("No existing settings found, creating new one")
123+
settings.Other = make(map[string]any)
124+
}
125+
126+
// Initialize yaml.schemas if it doesn't exist
127+
if settings.YAMLSchemas == nil {
128+
settings.YAMLSchemas = make(map[string]any)
129+
}
130+
131+
// Add schema mapping for workflow files
132+
schemaPath := "./.github/aw/main_workflow_schema.json"
133+
workflowPattern := ".github/workflows/*.md"
134+
135+
// Check if already configured
136+
if existingPattern, exists := settings.YAMLSchemas[schemaPath]; exists {
137+
if existingPattern == workflowPattern {
138+
vscodeConfigLog.Print("Schema mapping already configured, skipping update")
139+
if verbose {
140+
fmt.Fprintf(os.Stderr, "YAML schema mapping already configured in %s\n", settingsPath)
141+
}
142+
return nil
143+
}
144+
}
145+
146+
settings.YAMLSchemas[schemaPath] = workflowPattern
147+
148+
// Ensure yaml validation settings are enabled
149+
if settings.Other == nil {
150+
settings.Other = make(map[string]any)
151+
}
152+
if _, exists := settings.Other["yaml.validate"]; !exists {
153+
settings.Other["yaml.validate"] = true
154+
}
155+
if _, exists := settings.Other["yaml.hover"]; !exists {
156+
settings.Other["yaml.hover"] = true
157+
}
158+
if _, exists := settings.Other["yaml.completion"]; !exists {
159+
settings.Other["yaml.completion"] = true
160+
}
161+
162+
// Write settings file with proper indentation
163+
data, err := json.MarshalIndent(settings, "", " ")
164+
if err != nil {
165+
return fmt.Errorf("failed to marshal settings.json: %w", err)
166+
}
167+
168+
if err := os.WriteFile(settingsPath, data, 0644); err != nil {
169+
return fmt.Errorf("failed to write settings.json: %w", err)
170+
}
171+
vscodeConfigLog.Printf("Wrote settings to: %s", settingsPath)
172+
173+
return nil
174+
}
175+
176+
// ensureVSCodeExtensions creates or updates .vscode/extensions.json with RedHat YAML extension
177+
func ensureVSCodeExtensions(verbose bool) error {
178+
vscodeConfigLog.Print("Creating or updating .vscode/extensions.json")
179+
180+
// Create .vscode directory if it doesn't exist
181+
vscodeDir := ".vscode"
182+
if err := os.MkdirAll(vscodeDir, 0755); err != nil {
183+
return fmt.Errorf("failed to create .vscode directory: %w", err)
184+
}
185+
186+
extensionsPath := filepath.Join(vscodeDir, "extensions.json")
187+
188+
// Read existing extensions if they exist
189+
var extensions VSCodeExtensions
190+
if data, err := os.ReadFile(extensionsPath); err == nil {
191+
vscodeConfigLog.Printf("Reading existing extensions from: %s", extensionsPath)
192+
if err := json.Unmarshal(data, &extensions); err != nil {
193+
return fmt.Errorf("failed to parse existing extensions.json: %w", err)
194+
}
195+
} else {
196+
vscodeConfigLog.Print("No existing extensions file found, creating new one")
197+
extensions.Recommendations = []string{}
198+
}
199+
200+
// Check if RedHat YAML extension is already in recommendations
201+
redhatYAMLExt := "redhat.vscode-yaml"
202+
hasYAMLExt := false
203+
for _, ext := range extensions.Recommendations {
204+
if ext == redhatYAMLExt {
205+
hasYAMLExt = true
206+
break
207+
}
208+
}
209+
210+
if hasYAMLExt {
211+
vscodeConfigLog.Print("RedHat YAML extension already in recommendations, skipping update")
212+
if verbose {
213+
fmt.Fprintf(os.Stderr, "RedHat YAML extension already in %s\n", extensionsPath)
214+
}
215+
return nil
216+
}
217+
218+
// Add RedHat YAML extension to recommendations
219+
extensions.Recommendations = append(extensions.Recommendations, redhatYAMLExt)
220+
vscodeConfigLog.Printf("Added %s to recommendations", redhatYAMLExt)
221+
222+
// Write extensions file with proper indentation
223+
data, err := json.MarshalIndent(extensions, "", " ")
224+
if err != nil {
225+
return fmt.Errorf("failed to marshal extensions.json: %w", err)
226+
}
227+
228+
if err := os.WriteFile(extensionsPath, data, 0644); err != nil {
229+
return fmt.Errorf("failed to write extensions.json: %w", err)
230+
}
231+
vscodeConfigLog.Printf("Wrote extensions to: %s", extensionsPath)
232+
233+
return nil
234+
}

‎pkg/parser/schema.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1154,3 +1154,8 @@ func FindDeprecatedFieldsInFrontmatter(frontmatter map[string]any, deprecatedFie
11541154

11551155
return found
11561156
}
1157+
1158+
// GetMainWorkflowSchema returns the embedded main workflow schema JSON
1159+
func GetMainWorkflowSchema() string {
1160+
return mainWorkflowSchema
1161+
}

0 commit comments

Comments
 (0)