From bb922f19d957875f3c395c77e81a62f8a30257a3 Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Sun, 13 Sep 2026 16:50:40 +0000 Subject: [PATCH] Prepare CLI loading for ConfigKit configuration --- docs/roadmap/implementation.md | 2 ++ internal/cli/configkit_load.go | 37 +++++++++++++++++++ internal/cli/configkit_load_test.go | 52 +++++++++++++++++++++++++++ internal/core/config/loaded_config.go | 6 ++++ 4 files changed, 97 insertions(+) create mode 100644 internal/cli/configkit_load.go create mode 100644 internal/cli/configkit_load_test.go diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 3fa165bf..00bfea30 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -416,6 +416,8 @@ with no CLI activation or example migration. ## Stage 8 — Prepare Shared CLI Loading And Diagnostics +**Status:** Completed 2026-09-13. + **Goal:** Make one command-facing configuration entry point ready for cutover. **Work:** diff --git a/internal/cli/configkit_load.go b/internal/cli/configkit_load.go new file mode 100644 index 00000000..a4f5eece --- /dev/null +++ b/internal/cli/configkit_load.go @@ -0,0 +1,37 @@ +package cli + +import ( + "errors" + "fmt" + "io" + "os" + + configkit "gitea.maximumdirect.net/eric/ConfigKit" + "gitea.maximumdirect.net/eric/notarius/internal/core/config" +) + +func loadConfigKit(configPath, profile string, overrides []configkit.Override, opts Options) (*config.LoadedConfig, string, error) { + path, err := discoverConfigPath(configPath, opts) + if err != nil { + return nil, "", err + } + workingDirectory, err := os.Getwd() + if err != nil { + return nil, "", fmt.Errorf("determine invocation directory: %w", err) + } + loaded, err := config.Load(path, config.LoadOptions{Profile: profile, Overrides: overrides, InvocationDirectory: workingDirectory}) + if err != nil { + return nil, "", err + } + return loaded, path, nil +} + +func writeConfigLoadError(output io.Writer, err error) { + var loadError *configkit.LoadError + if errors.As(err, &loadError) { + if writeErr := configkit.WriteDiagnostics(output, loadError.Diagnostics()); writeErr == nil { + return + } + } + fmt.Fprintf(output, "notarius: %v\n", err) +} diff --git a/internal/cli/configkit_load_test.go b/internal/cli/configkit_load_test.go new file mode 100644 index 00000000..808a88d5 --- /dev/null +++ b/internal/cli/configkit_load_test.go @@ -0,0 +1,52 @@ +package cli + +import ( + "bytes" + "os" + "path/filepath" + "strings" + "testing" + + configkit "gitea.maximumdirect.net/eric/ConfigKit" +) + +func TestLoadConfigKitUsesDiscoveryProfileAndOverrides(t *testing.T) { + t.Parallel() + root := t.TempDir() + path := filepath.Join(root, "notarius.yml") + writeConfigKitFile(t, path, "version: 5\npipelines:\n base:\n input: {module: input}\n") + profiles := filepath.Join(root, "profiles") + writeConfigKitFile(t, filepath.Join(profiles, "development.yml"), "pipelines:\n profiled:\n input: {module: input}\n") + loaded, gotPath, err := loadConfigKit(path, "development", []configkit.Override{{Path: configkit.Keys("output", "directory"), Value: "generated", Flag: "--output"}}, commandContractOptions(t)) + if err != nil { + t.Fatalf("loadConfigKit() error = %v", err) + } + if gotPath != path || loaded.Config.Pipelines["profiled"].Input.Module != "input" || loaded.Config.Output.Directory == "generated" { + t.Fatalf("loaded = %#v, path=%q", loaded.Config, gotPath) + } +} + +func TestWriteConfigLoadErrorUsesSafeDiagnostics(t *testing.T) { + t.Parallel() + path := filepath.Join(t.TempDir(), "invalid.yml") + writeConfigKitFile(t, path, "version: 4\n") + _, _, err := loadConfigKit(path, "", nil, commandContractOptions(t)) + if err == nil { + t.Fatal("loadConfigKit() error = nil") + } + var output bytes.Buffer + writeConfigLoadError(&output, err) + if !strings.Contains(output.String(), "version") || strings.Contains(output.String(), "notarius: configkit") { + t.Fatalf("diagnostics = %q", output.String()) + } +} + +func writeConfigKitFile(t *testing.T, path, contents string) { + t.Helper() + if err := os.MkdirAll(filepath.Dir(path), 0o700); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(path, []byte(contents), 0o600); err != nil { + t.Fatal(err) + } +} diff --git a/internal/core/config/loaded_config.go b/internal/core/config/loaded_config.go index 7fd245c7..5fb157b9 100644 --- a/internal/core/config/loaded_config.go +++ b/internal/core/config/loaded_config.go @@ -53,6 +53,12 @@ func loadConfig(path string, options LoadOptions) (*LoadedConfig, error) { return newLoadedConfig(result) } +// Load reads a version 5 configuration file into runtime configuration and a +// private inspection snapshot. +func Load(path string, options LoadOptions) (*LoadedConfig, error) { + return loadConfig(path, options) +} + func loadConfigSource(source configkit.Source, options LoadOptions) (*LoadedConfig, error) { result, err := loadDocumentSource(source, options) if err != nil {