// Package wizard provides configuration export functionality. package wizard import ( "encoding/json" "fmt" "os" "path/filepath" "github.com/goccy/go-yaml" "github.com/ivuorinen/gh-action-readme/internal" ) // ExportFormat represents the supported export formats. type ExportFormat string const ( // FormatYAML exports configuration as YAML. FormatYAML ExportFormat = "yaml" // FormatJSON exports configuration as JSON. FormatJSON ExportFormat = "json" // FormatTOML exports configuration as TOML. FormatTOML ExportFormat = "toml" ) // ConfigExporter handles exporting configuration to various formats. type ConfigExporter struct { output *internal.ColoredOutput } // NewConfigExporter creates a new configuration exporter. func NewConfigExporter(output *internal.ColoredOutput) *ConfigExporter { return &ConfigExporter{ output: output, } } // ExportConfig exports the configuration to the specified format and path. func (e *ConfigExporter) ExportConfig(config *internal.AppConfig, format ExportFormat, outputPath string) error { // Create output directory if it doesn't exist if err := os.MkdirAll(filepath.Dir(outputPath), 0750); err != nil { // #nosec G301 -- output directory permissions return fmt.Errorf("failed to create output directory: %w", err) } switch format { case FormatYAML: return e.exportYAML(config, outputPath) case FormatJSON: return e.exportJSON(config, outputPath) case FormatTOML: return e.exportTOML(config, outputPath) default: return fmt.Errorf("unsupported export format: %s", format) } } // GetSupportedFormats returns the list of supported export formats. func (e *ConfigExporter) GetSupportedFormats() []ExportFormat { return []ExportFormat{FormatYAML, FormatJSON, FormatTOML} } // GetDefaultOutputPath returns the default output path for a given format. func (e *ConfigExporter) GetDefaultOutputPath(format ExportFormat) (string, error) { configPath, err := internal.GetConfigPath() if err != nil { return "", fmt.Errorf("failed to get config directory: %w", err) } dir := filepath.Dir(configPath) switch format { case FormatYAML: return filepath.Join(dir, "config.yaml"), nil case FormatJSON: return filepath.Join(dir, "config.json"), nil case FormatTOML: return filepath.Join(dir, "config.toml"), nil default: return "", fmt.Errorf("unsupported format: %s", format) } } // exportYAML exports configuration as YAML. func (e *ConfigExporter) exportYAML(config *internal.AppConfig, outputPath string) error { // Create a clean config without sensitive data for export exportConfig := e.sanitizeConfig(config) file, err := os.Create(outputPath) // #nosec G304 -- output path from function parameter if err != nil { return fmt.Errorf("failed to create YAML file: %w", err) } defer func() { _ = file.Close() // File will be closed, error not actionable in defer }() encoder := yaml.NewEncoder(file, yaml.Indent(2)) // Add header comment _, _ = file.WriteString("# gh-action-readme configuration file\n") _, _ = file.WriteString("# Generated by the interactive configuration wizard\n\n") if err := encoder.Encode(exportConfig); err != nil { return fmt.Errorf("failed to encode YAML: %w", err) } e.output.Success("Configuration exported to: %s", outputPath) return nil } // exportJSON exports configuration as JSON. func (e *ConfigExporter) exportJSON(config *internal.AppConfig, outputPath string) error { // Create a clean config without sensitive data for export exportConfig := e.sanitizeConfig(config) file, err := os.Create(outputPath) // #nosec G304 -- output path from function parameter if err != nil { return fmt.Errorf("failed to create JSON file: %w", err) } defer func() { _ = file.Close() // File will be closed, error not actionable in defer }() encoder := json.NewEncoder(file) encoder.SetIndent("", " ") if err := encoder.Encode(exportConfig); err != nil { return fmt.Errorf("failed to encode JSON: %w", err) } e.output.Success("Configuration exported to: %s", outputPath) return nil } // exportTOML exports configuration as TOML. func (e *ConfigExporter) exportTOML(config *internal.AppConfig, outputPath string) error { // For now, we'll use a basic TOML export since the TOML library adds dependencies // In a full implementation, you would use "github.com/BurntSushi/toml" exportConfig := e.sanitizeConfig(config) file, err := os.Create(outputPath) // #nosec G304 -- output path from function parameter if err != nil { return fmt.Errorf("failed to create TOML file: %w", err) } defer func() { _ = file.Close() // File will be closed, error not actionable in defer }() // Write TOML header _, _ = file.WriteString("# gh-action-readme configuration file\n") _, _ = file.WriteString("# Generated by the interactive configuration wizard\n\n") // Basic TOML export (simplified version) e.writeTOMLConfig(file, exportConfig) e.output.Success("Configuration exported to: %s", outputPath) return nil } // sanitizeConfig removes sensitive information from config for export. func (e *ConfigExporter) sanitizeConfig(config *internal.AppConfig) *internal.AppConfig { // Create a copy of the config sanitized := *config // Remove sensitive information sanitized.GitHubToken = "" // Never export tokens sanitized.RepoOverrides = nil // Don't export repo overrides // Remove empty/default values to keep the config clean if sanitized.Organization == "" { sanitized.Organization = "" } if sanitized.Repository == "" { sanitized.Repository = "" } if sanitized.Version == "" { sanitized.Version = "" } // Remove legacy fields if they match defaults defaults := internal.DefaultAppConfig() if sanitized.Template == defaults.Template { sanitized.Template = "" } if sanitized.Header == defaults.Header { sanitized.Header = "" } if sanitized.Footer == defaults.Footer { sanitized.Footer = "" } if sanitized.Schema == defaults.Schema { sanitized.Schema = "" } return &sanitized } // writeTOMLConfig writes a basic TOML configuration. func (e *ConfigExporter) writeTOMLConfig(file *os.File, config *internal.AppConfig) { e.writeRepositorySection(file, config) e.writeTemplateSection(file, config) e.writeFeaturesSection(file, config) e.writeBehaviorSection(file, config) e.writeWorkflowSection(file, config) e.writePermissionsSection(file, config) e.writeVariablesSection(file, config) } // writeRepositorySection writes the repository information section. func (e *ConfigExporter) writeRepositorySection(file *os.File, config *internal.AppConfig) { _, _ = fmt.Fprintf(file, "# Repository Information\n") if config.Organization != "" { _, _ = fmt.Fprintf(file, "organization = %q\n", config.Organization) } if config.Repository != "" { _, _ = fmt.Fprintf(file, "repository = %q\n", config.Repository) } if config.Version != "" { _, _ = fmt.Fprintf(file, "version = %q\n", config.Version) } } // writeTemplateSection writes the template settings section. func (e *ConfigExporter) writeTemplateSection(file *os.File, config *internal.AppConfig) { _, _ = fmt.Fprintf(file, "\n# Template Settings\n") _, _ = fmt.Fprintf(file, "theme = %q\n", config.Theme) _, _ = fmt.Fprintf(file, "output_format = %q\n", config.OutputFormat) _, _ = fmt.Fprintf(file, "output_dir = %q\n", config.OutputDir) } // writeFeaturesSection writes the features section. func (e *ConfigExporter) writeFeaturesSection(file *os.File, config *internal.AppConfig) { _, _ = fmt.Fprintf(file, "\n# Features\n") _, _ = fmt.Fprintf(file, "analyze_dependencies = %t\n", config.AnalyzeDependencies) _, _ = fmt.Fprintf(file, "show_security_info = %t\n", config.ShowSecurityInfo) } // writeBehaviorSection writes the behavior section. func (e *ConfigExporter) writeBehaviorSection(file *os.File, config *internal.AppConfig) { _, _ = fmt.Fprintf(file, "\n# Behavior\n") _, _ = fmt.Fprintf(file, "verbose = %t\n", config.Verbose) _, _ = fmt.Fprintf(file, "quiet = %t\n", config.Quiet) } // writeWorkflowSection writes the workflow requirements section. func (e *ConfigExporter) writeWorkflowSection(file *os.File, config *internal.AppConfig) { if len(config.RunsOn) == 0 { return } _, _ = fmt.Fprintf(file, "\n# Workflow Requirements\n") _, _ = fmt.Fprintf(file, "runs_on = [") for i, runner := range config.RunsOn { if i > 0 { _, _ = fmt.Fprintf(file, ", ") } _, _ = fmt.Fprintf(file, "%q", runner) } _, _ = fmt.Fprintf(file, "]\n") } // writePermissionsSection writes the permissions section. func (e *ConfigExporter) writePermissionsSection(file *os.File, config *internal.AppConfig) { if len(config.Permissions) == 0 { return } _, _ = fmt.Fprintf(file, "\n[permissions]\n") for key, value := range config.Permissions { _, _ = fmt.Fprintf(file, "%s = %q\n", key, value) } } // writeVariablesSection writes the variables section. func (e *ConfigExporter) writeVariablesSection(file *os.File, config *internal.AppConfig) { if len(config.Variables) == 0 { return } _, _ = fmt.Fprintf(file, "\n[variables]\n") for key, value := range config.Variables { _, _ = fmt.Fprintf(file, "%s = %q\n", key, value) } }