Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/run.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,9 @@ jobs:
- name: Generate static site
run: |
set -e # Exit on error
./bin/cdprun --log-level debug sitegen --db downloads.db --out _site
./bin/cdprun --config runtime-registry.yaml --log-level debug sitegen \
--db downloads.db \
--out _site
echo "Site generation completed with exit code: $?"

- name: Verify site generation
Expand Down
18 changes: 16 additions & 2 deletions cmd/sitegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/urfave/cli/v2"

appcli "github.com/clean-dependency-project/cdprun/internal/cli"
"github.com/clean-dependency-project/cdprun/internal/config"
"github.com/clean-dependency-project/cdprun/internal/sitegen"
"github.com/clean-dependency-project/cdprun/internal/storage"
)
Expand Down Expand Up @@ -40,6 +41,11 @@ func main() {
Usage: "log level (debug, info, warn, error)",
EnvVars: []string{"SITEGEN_LOG_LEVEL"},
},
&cli.StringFlag{
Name: "unsupported-file",
Usage: "path to YAML/JSON file listing unsupported runtime version prefixes (overrides registry config)",
EnvVars: []string{"SITEGEN_UNSUPPORTED_FILE"},
},
},
Action: runSitegen,
}
Expand Down Expand Up @@ -70,13 +76,21 @@ func runSitegen(c *cli.Context) error {
}
}()

// Resolve unsupported-versions file: flag overrides, else empty (no filtering).
unsupportedFile := c.String("unsupported-file")
unsupportedCfg, err := config.LoadUnsupportedConfig(unsupportedFile)
if err != nil {
return err
}

// Create generator
generator := sitegen.NewGenerator(db, stdout)

// Generate site
opts := sitegen.GenerateOptions{
OutputDir: c.String("out"),
DryRun: c.Bool("dry-run"),
OutputDir: c.String("out"),
DryRun: c.Bool("dry-run"),
UnsupportedVersions: unsupportedCfg,
}

if err := generator.Generate(context.Background(), opts); err != nil {
Expand Down
106 changes: 98 additions & 8 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ func NewApp() *cli.App {
Name: "dry-run",
Usage: "validate without writing files",
},
&cli.StringFlag{
Name: "unsupported-file",
Usage: "path to YAML/JSON file listing unsupported runtime version prefixes (overrides registry config)",
EnvVars: []string{"SITEGEN_UNSUPPORTED_FILE"},
},
},
Action: sitegenCommand,
},
Expand Down Expand Up @@ -955,6 +960,23 @@ func downloadSingleRuntime(c *cli.Context, manager *runtime.Manager, cfg *config
}
}

// Load unsupported-versions configuration if specified.
unsupportedCfg, unsuppErr := config.LoadUnsupportedConfig(cfg.Config.UnsupportedFile)
if unsuppErr != nil {
stderr.Warn("failed to load unsupported versions config", "unsupported_file", cfg.Config.UnsupportedFile, "error", unsuppErr)
unsupportedCfg = config.UnsupportedConfig{}
} else {
ruleCount := 0
for _, rules := range unsupportedCfg {
ruleCount += len(rules)
}
stdout.Debug("loaded unsupported versions config", "unsupported_file", cfg.Config.UnsupportedFile, "rule_count", ruleCount)
}

// For batch path (no explicit --version): skip versions marked unsupported.
// For explicit --version path: warn but still proceed (user intent override).
versionsToDownload = applyUnsupportedFilter(runtimeName, version, versionsToDownload, unsupportedCfg, stderr)

// Download all versions
var allResults []DownloadResult
totalSuccess := 0
Expand Down Expand Up @@ -1220,21 +1242,35 @@ func sitegenCommand(c *cli.Context) error {
logLevel := ParseLogLevelOrDefault(c.String("log-level"))
stdout, stderr := NewLoggers(logLevel)

// Determine database path
// Always load the registry config so we can read UnsupportedFile even when --db is supplied.
configPath := c.String("config")
cfg, cfgErr := config.LoadConfig(configPath)

// Determine database path: flag takes precedence, then registry config.
dbPath := c.String("db")
if dbPath == "" {
// Try to get from config
configPath := c.String("config")
cfg, err := config.LoadConfig(configPath)
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
if cfgErr != nil {
return fmt.Errorf("failed to load config: %w", cfgErr)
}
dbPath = cfg.Config.Storage.DatabasePath
if dbPath == "" {
return fmt.Errorf("database path not specified and not found in config")
}
}

// Resolve unsupported-versions file: --unsupported-file flag overrides, then registry config.
unsupportedFile := c.String("unsupported-file")
if unsupportedFile == "" && cfgErr == nil {
unsupportedFile = cfg.Config.UnsupportedFile
}
unsupportedCfg, err := config.LoadUnsupportedConfig(unsupportedFile)
if err != nil {
return fmt.Errorf("failed to load unsupported versions config: %w", err)
}
if unsupportedFile != "" {
stdout.Info("loaded unsupported versions config", "file", unsupportedFile, "runtimes", len(unsupportedCfg))
}

// Open database
db, err := storage.InitDB(storage.Config{
DatabasePath: dbPath,
Expand All @@ -1254,8 +1290,9 @@ func sitegenCommand(c *cli.Context) error {

// Generate site
opts := sitegen.GenerateOptions{
OutputDir: outputDir,
DryRun: dryRun,
OutputDir: outputDir,
DryRun: dryRun,
UnsupportedVersions: unsupportedCfg,
}

if err := generator.Generate(c.Context, opts); err != nil {
Expand All @@ -1265,3 +1302,56 @@ func sitegenCommand(c *cli.Context) error {
stdout.Info("site generation completed successfully")
return nil
}

// applyUnsupportedFilter applies the unsupported-versions policy to versionsToDownload.
//
// Batch mode (requestedVersion == ""): versions whose LatestPatch matches a rule are
// removed from the list and a structured Warn is emitted for each one.
//
// Explicit mode (requestedVersion != ""): versions are kept but a Warn is still emitted
// so operators are informed; the user knowingly requested an unsupported version.
func applyUnsupportedFilter(
runtimeName, requestedVersion string,
versions []runtime.VersionInfo,
uc config.UnsupportedConfig,
logger *slog.Logger,
) []runtime.VersionInfo {
if len(uc) == 0 {
return versions
}
if requestedVersion != "" {
// Explicit: warn only.
for _, vi := range versions {
if uc.IsVersionUnsupported(runtimeName, vi.LatestPatch) {
rule := uc.FindMatchingRule(runtimeName, vi.LatestPatch)
eolDate := ""
if rule != nil {
eolDate = rule.EOLDate
}
logger.Warn("downloading explicitly requested version that is unsupported",
"runtime", runtimeName,
"version", vi.LatestPatch,
"eol_date", eolDate)
}
}
return versions
}
// Batch: skip unsupported.
kept := versions[:0:0]
for _, vi := range versions {
if uc.IsVersionUnsupported(runtimeName, vi.LatestPatch) {
rule := uc.FindMatchingRule(runtimeName, vi.LatestPatch)
eolDate := ""
if rule != nil {
eolDate = rule.EOLDate
}
logger.Warn("skipping unsupported version",
"runtime", runtimeName,
"version", vi.LatestPatch,
"eol_date", eolDate)
continue
}
kept = append(kept, vi)
}
return kept
}
98 changes: 98 additions & 0 deletions internal/cli/unsupported_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package cli

import (
"bytes"
"log/slog"
"testing"

"github.com/clean-dependency-project/cdprun/internal/config"
"github.com/clean-dependency-project/cdprun/internal/runtime"
)

func makeVersionInfos(latestPatches ...string) []runtime.VersionInfo {
vis := make([]runtime.VersionInfo, len(latestPatches))
for i, lp := range latestPatches {
vis[i] = runtime.VersionInfo{Version: lp, LatestPatch: lp}
}
return vis
}

func discardLogger() *slog.Logger {
return slog.New(slog.NewTextHandler(&bytes.Buffer{}, nil))
}

func TestApplyUnsupportedFilter_BatchSkip(t *testing.T) {
uc := config.UnsupportedConfig{
"nodejs": {
{Version: "16", EOLDate: "2023-09-11"},
{Version: "14", EOLDate: "2023-04-30"},
},
}
versions := makeVersionInfos("22.15.0", "20.19.1", "16.20.2", "14.21.3")

got := applyUnsupportedFilter("nodejs", "", versions, uc, discardLogger())

if len(got) != 2 {
t.Fatalf("expected 2 versions after batch filter, got %d: %v", len(got), got)
}
for _, vi := range got {
if vi.LatestPatch == "16.20.2" || vi.LatestPatch == "14.21.3" {
t.Errorf("unsupported version %q should have been filtered out", vi.LatestPatch)
}
}
}

func TestApplyUnsupportedFilter_ExplicitWarnOnly(t *testing.T) {
uc := config.UnsupportedConfig{
"nodejs": {{Version: "16", EOLDate: "2023-09-11"}},
}
// Explicit --version path: version is NOT empty.
versions := makeVersionInfos("16.20.2")

got := applyUnsupportedFilter("nodejs", "16", versions, uc, discardLogger())

// Version must be KEPT (warn only, not removed).
if len(got) != 1 {
t.Fatalf("expected 1 version (kept) for explicit path, got %d", len(got))
}
if got[0].LatestPatch != "16.20.2" {
t.Errorf("expected 16.20.2 to be kept, got %q", got[0].LatestPatch)
}
}

func TestApplyUnsupportedFilter_EmptyConfig(t *testing.T) {
versions := makeVersionInfos("22.15.0", "20.19.1", "16.20.2")

got := applyUnsupportedFilter("nodejs", "", versions, config.UnsupportedConfig{}, discardLogger())

if len(got) != 3 {
t.Errorf("expected all 3 versions to pass through empty config, got %d", len(got))
}
}

func TestApplyUnsupportedFilter_UnknownRuntime(t *testing.T) {
uc := config.UnsupportedConfig{
"nodejs": {{Version: "16", EOLDate: "2023-09-11"}},
}
versions := makeVersionInfos("8.11.0") // "tomcat" has no rules

got := applyUnsupportedFilter("tomcat", "", versions, uc, discardLogger())

if len(got) != 1 {
t.Errorf("expected 1 version for unknown runtime, got %d", len(got))
}
}

func TestApplyUnsupportedFilter_FalsePositive(t *testing.T) {
uc := config.UnsupportedConfig{
"nodejs": {{Version: "16", EOLDate: "2023-09-11"}},
}
// "160.0.1" must NOT be filtered by prefix "16"
versions := makeVersionInfos("160.0.1", "22.15.0")

got := applyUnsupportedFilter("nodejs", "", versions, uc, discardLogger())

if len(got) != 2 {
t.Errorf("expected 160.0.1 to NOT be filtered by prefix '16', got %v", got)
}
}
3 changes: 2 additions & 1 deletion internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ type GlobalConfig struct {
DownloadTimeout string `yaml:"download_timeout"`
AutoDownloadAllPlatforms bool `yaml:"auto_download_all_platforms"`
Storage StorageConfig `yaml:"storage"`
IgnoreFile string `yaml:"ignore_file"` // Path to JSON file listing versions to ignore per runtime
IgnoreFile string `yaml:"ignore_file"` // Path to file listing platform-specific versions to ignore
UnsupportedFile string `yaml:"unsupported_file"` // Path to file listing unsupported runtime version prefixes
PackagingExecution PackagingExecutionConfig `yaml:"packaging_execution"`
}

Expand Down
Loading
Loading