mirror of
https://github.com/ivuorinen/gh-action-readme.git
synced 2026-01-26 11:14:04 +00:00
* chore(lint): added nlreturn, run linting * chore(lint): replace some fmt.Sprintf calls * chore(lint): replace fmt.Sprintf with strconv * chore(lint): add goconst, use http lib for status codes, and methods * chore(lint): use errors lib, errCodes from internal/errors * chore(lint): dupl, thelper and usetesting * chore(lint): fmt.Errorf %v to %w, more linters * chore(lint): paralleltest, where possible * perf(test): optimize test performance by 78% - Implement shared binary building with package-level cache to eliminate redundant builds - Add strategic parallelization to 15+ tests while preserving environment variable isolation - Implement thread-safe fixture caching with RWMutex to reduce I/O operations - Remove unnecessary working directory changes by leveraging embedded templates - Add embedded template system with go:embed directive for reliable template resolution - Fix linting issues: rename sharedBinaryError to errSharedBinary, add nolint directive Performance improvements: - Total test execution time: 12+ seconds → 2.7 seconds (78% faster) - Binary build overhead: 14+ separate builds → 1 shared build (93% reduction) - Parallel execution: Limited → 15+ concurrent tests (60-70% better CPU usage) - I/O operations: 66+ fixture reads → cached with sync.RWMutex (50% reduction) All tests maintain 100% success rate and coverage while running nearly 4x faster.
289 lines
8.9 KiB
Go
289 lines
8.9 KiB
Go
// Package wizard provides configuration export functionality.
|
|
package wizard
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"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)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
encoder.SetIndent(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)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|