Files
gh-action-readme/internal/parser.go
Ismo Vuorinen 4f12c4d3dd feat(lint): add many linters, make all the tests run fast! (#23)
* 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.
2025-08-06 15:28:09 +03:00

102 lines
2.6 KiB
Go

package internal
import (
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
// ActionYML models the action.yml metadata (fields are updateable as schema evolves).
type ActionYML struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Inputs map[string]ActionInput `yaml:"inputs"`
Outputs map[string]ActionOutput `yaml:"outputs"`
Runs map[string]any `yaml:"runs"`
Branding *Branding `yaml:"branding,omitempty"`
// Add more fields as the schema evolves
}
// ActionInput represents an input parameter for a GitHub Action.
type ActionInput struct {
Description string `yaml:"description"`
Required bool `yaml:"required"`
Default any `yaml:"default"`
}
// ActionOutput represents an output parameter for a GitHub Action.
type ActionOutput struct {
Description string `yaml:"description"`
}
// Branding represents the branding configuration for a GitHub Action.
type Branding struct {
Icon string `yaml:"icon"`
Color string `yaml:"color"`
}
// ParseActionYML reads and parses action.yml from given path.
func ParseActionYML(path string) (*ActionYML, error) {
f, err := os.Open(path) // #nosec G304 -- path from function parameter
if err != nil {
return nil, err
}
defer func() {
_ = f.Close() // Ignore close error in defer
}()
var a ActionYML
dec := yaml.NewDecoder(f)
if err := dec.Decode(&a); err != nil {
return nil, err
}
return &a, nil
}
// DiscoverActionFiles finds action.yml and action.yaml files in the given directory.
// This consolidates the file discovery logic from both generator.go and dependencies/parser.go.
func DiscoverActionFiles(dir string, recursive bool) ([]string, error) {
var actionFiles []string
// Check if dir exists
if _, err := os.Stat(dir); os.IsNotExist(err) {
return nil, fmt.Errorf("directory does not exist: %s", dir)
}
if recursive {
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if info.IsDir() {
return nil
}
// Check for action.yml or action.yaml files
filename := strings.ToLower(info.Name())
if filename == "action.yml" || filename == "action.yaml" {
actionFiles = append(actionFiles, path)
}
return nil
})
if err != nil {
return nil, fmt.Errorf("failed to walk directory %s: %w", dir, err)
}
} else {
// Check only the specified directory
for _, filename := range []string{"action.yml", "action.yaml"} {
path := filepath.Join(dir, filename)
if _, err := os.Stat(path); err == nil {
actionFiles = append(actionFiles, path)
}
}
}
return actionFiles, nil
}