* ci: consolidate duplicate workflows and merge CodeQL configs Remove 17 duplicate workflow files that were split copies of the combined originals. Each family (CI, CodeQL, Coverage, PR Build, Alpha Release) had the same job duplicated across separate push/pull_request/schedule/manual trigger files. Merge codeql.yml and codescan.yml into a single codeql.yml with a language matrix covering go, javascript-typescript, python, and actions — matching the previous default setup coverage. Remaining workflows (one per family): - ci.yml (push + PR + manual) - codeql.yml (push + PR + schedule, all languages) - coverage.yml (push + PR + manual) - alpha-release.yml (push + manual) - pr-build.yml (PR + manual) - release.yml (tag push) - agent-verify.yml, auto-label.yml, auto-project.yml Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add collect, config, crypt, plugin packages and fix all lint issues Add four new infrastructure packages with CLI commands: - pkg/config: layered configuration (defaults → file → env → flags) - pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums) - pkg/plugin: plugin system with GitHub-based install/update/remove - pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate) Fix all golangci-lint issues across the entire codebase (~100 errcheck, staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that `core go qa` passes with 0 issues. Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
204 lines
6.2 KiB
Go
204 lines
6.2 KiB
Go
// github_config.go defines configuration types for GitHub repository setup.
|
|
//
|
|
// Configuration is loaded from .core/github.yaml and supports environment
|
|
// variable expansion using ${VAR} or ${VAR:-default} syntax.
|
|
|
|
package setup
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
|
|
coreio "github.com/host-uk/core/pkg/io"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// GitHubConfig represents the full GitHub setup configuration.
|
|
type GitHubConfig struct {
|
|
Version int `yaml:"version"`
|
|
Labels []LabelConfig `yaml:"labels"`
|
|
Webhooks map[string]WebhookConfig `yaml:"webhooks"`
|
|
BranchProtection []BranchProtectionConfig `yaml:"branch_protection"`
|
|
Security SecurityConfig `yaml:"security"`
|
|
}
|
|
|
|
// LabelConfig defines a GitHub issue/PR label.
|
|
type LabelConfig struct {
|
|
Name string `yaml:"name"`
|
|
Color string `yaml:"color"`
|
|
Description string `yaml:"description"`
|
|
}
|
|
|
|
// WebhookConfig defines a GitHub webhook configuration.
|
|
type WebhookConfig struct {
|
|
URL string `yaml:"url"` // Webhook URL (supports ${ENV_VAR})
|
|
ContentType string `yaml:"content_type"` // json or form (default: json)
|
|
Secret string `yaml:"secret"` // Optional secret (supports ${ENV_VAR})
|
|
Events []string `yaml:"events"` // Events to trigger on
|
|
Active *bool `yaml:"active"` // Whether webhook is active (default: true)
|
|
}
|
|
|
|
// BranchProtectionConfig defines branch protection rules.
|
|
type BranchProtectionConfig struct {
|
|
Branch string `yaml:"branch"`
|
|
RequiredReviews int `yaml:"required_reviews"`
|
|
DismissStale bool `yaml:"dismiss_stale"`
|
|
RequireCodeOwnerReviews bool `yaml:"require_code_owner_reviews"`
|
|
RequiredStatusChecks []string `yaml:"required_status_checks"`
|
|
RequireLinearHistory bool `yaml:"require_linear_history"`
|
|
AllowForcePushes bool `yaml:"allow_force_pushes"`
|
|
AllowDeletions bool `yaml:"allow_deletions"`
|
|
EnforceAdmins bool `yaml:"enforce_admins"`
|
|
RequireConversationResolution bool `yaml:"require_conversation_resolution"`
|
|
}
|
|
|
|
// SecurityConfig defines repository security settings.
|
|
type SecurityConfig struct {
|
|
DependabotAlerts bool `yaml:"dependabot_alerts"`
|
|
DependabotSecurityUpdates bool `yaml:"dependabot_security_updates"`
|
|
SecretScanning bool `yaml:"secret_scanning"`
|
|
SecretScanningPushProtection bool `yaml:"push_protection"`
|
|
}
|
|
|
|
// LoadGitHubConfig reads and parses a GitHub configuration file.
|
|
func LoadGitHubConfig(path string) (*GitHubConfig, error) {
|
|
data, err := coreio.Local.Read(path)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to read config file: %w", err)
|
|
}
|
|
|
|
// Expand environment variables before parsing
|
|
expanded := expandEnvVars(data)
|
|
|
|
var config GitHubConfig
|
|
if err := yaml.Unmarshal([]byte(expanded), &config); err != nil {
|
|
return nil, fmt.Errorf("failed to parse config file: %w", err)
|
|
}
|
|
|
|
// Set defaults
|
|
for i := range config.Webhooks {
|
|
wh := config.Webhooks[i]
|
|
if wh.ContentType == "" {
|
|
wh.ContentType = "json"
|
|
}
|
|
if wh.Active == nil {
|
|
active := true
|
|
wh.Active = &active
|
|
}
|
|
config.Webhooks[i] = wh
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// envVarPattern matches ${VAR} or ${VAR:-default} patterns.
|
|
var envVarPattern = regexp.MustCompile(`\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}`)
|
|
|
|
// expandEnvVars expands environment variables in the input string.
|
|
// Supports ${VAR} and ${VAR:-default} syntax.
|
|
func expandEnvVars(input string) string {
|
|
return envVarPattern.ReplaceAllStringFunc(input, func(match string) string {
|
|
// Parse the match
|
|
submatch := envVarPattern.FindStringSubmatch(match)
|
|
if len(submatch) < 2 {
|
|
return match
|
|
}
|
|
|
|
varName := submatch[1]
|
|
defaultValue := ""
|
|
if len(submatch) >= 3 {
|
|
defaultValue = submatch[2]
|
|
}
|
|
|
|
// Look up the environment variable
|
|
if value, ok := os.LookupEnv(varName); ok {
|
|
return value
|
|
}
|
|
return defaultValue
|
|
})
|
|
}
|
|
|
|
// FindGitHubConfig searches for github.yaml in common locations.
|
|
// Search order:
|
|
// 1. Specified path (if non-empty)
|
|
// 2. .core/github.yaml (relative to registry)
|
|
// 3. github.yaml (relative to registry)
|
|
func FindGitHubConfig(registryDir, specifiedPath string) (string, error) {
|
|
if specifiedPath != "" {
|
|
if coreio.Local.IsFile(specifiedPath) {
|
|
return specifiedPath, nil
|
|
}
|
|
return "", fmt.Errorf("config file not found: %s", specifiedPath)
|
|
}
|
|
|
|
// Search in common locations (using filepath.Join for OS-portable paths)
|
|
candidates := []string{
|
|
filepath.Join(registryDir, ".core", "github.yaml"),
|
|
filepath.Join(registryDir, "github.yaml"),
|
|
}
|
|
|
|
for _, path := range candidates {
|
|
if coreio.Local.IsFile(path) {
|
|
return path, nil
|
|
}
|
|
}
|
|
|
|
return "", fmt.Errorf("github.yaml not found in %s/.core/ or %s/", registryDir, registryDir)
|
|
}
|
|
|
|
// Validate checks the configuration for errors.
|
|
func (c *GitHubConfig) Validate() error {
|
|
if c.Version != 1 {
|
|
return fmt.Errorf("unsupported config version: %d (expected 1)", c.Version)
|
|
}
|
|
|
|
// Validate labels
|
|
for i, label := range c.Labels {
|
|
if label.Name == "" {
|
|
return fmt.Errorf("label %d: name is required", i+1)
|
|
}
|
|
if label.Color == "" {
|
|
return fmt.Errorf("label %q: color is required", label.Name)
|
|
}
|
|
// Validate color format (hex without #)
|
|
if !isValidHexColor(label.Color) {
|
|
return fmt.Errorf("label %q: invalid color %q (expected 6-digit hex without #)", label.Name, label.Color)
|
|
}
|
|
}
|
|
|
|
// Validate webhooks (skip those with empty URLs - allows optional webhooks via env vars)
|
|
for name, wh := range c.Webhooks {
|
|
if wh.URL == "" {
|
|
// Empty URL is allowed - webhook will be skipped during sync
|
|
continue
|
|
}
|
|
if len(wh.Events) == 0 {
|
|
return fmt.Errorf("webhook %q: at least one event is required", name)
|
|
}
|
|
}
|
|
|
|
// Validate branch protection
|
|
for i, bp := range c.BranchProtection {
|
|
if bp.Branch == "" {
|
|
return fmt.Errorf("branch_protection %d: branch is required", i+1)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// isValidHexColor checks if a string is a valid 6-digit hex color (without #).
|
|
func isValidHexColor(color string) bool {
|
|
if len(color) != 6 {
|
|
return false
|
|
}
|
|
for _, c := range strings.ToLower(color) {
|
|
if (c < '0' || c > '9') && (c < 'a' || c > 'f') {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|