From 667bf24a8ac2c2b4c4f9befd7f77b22f0a156a27 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 18 Apr 2026 16:22:05 +0300 Subject: refactor: extract migration logic into internal/migrate package (task q6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move all geheim→KeePass migration business logic from internal/cli/migrate_kdbx.go into a new internal/migrate package (migrator.go, kdbx_store.go). The CLI layer is now a thin orchestrator: parse flags, open KDBX, call migrate.Run, save, report. Key design decisions: - migrate.Run accepts separate logFn (stdout info) and warnFn (stderr errors) so per-entry errors correctly route to stderr via warn(), not stdout via logMsg() - migrate.Options contains only DryRun; DBPath/BinaryOutDir removed (CLI-only concerns) - StoreWalker interface is narrow, avoiding a direct dependency on backend.Backend - cli_paths.go extracts readPasswordFile/resolveHomeDir/expandHome shared within cli - kdbx_store.go deleted from cli (moved to internal/migrate) - Duplicate TestExtractPasswordFromContent removed from kdbx_store_test.go - Custom contains/containsStr helpers replaced with strings.Contains in tests - Dry-run test now asserts logged message content Co-Authored-By: Claude Sonnet 4.6 --- internal/cli/cli_paths.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 internal/cli/cli_paths.go (limited to 'internal/cli/cli_paths.go') diff --git a/internal/cli/cli_paths.go b/internal/cli/cli_paths.go new file mode 100644 index 0000000..5eae85a --- /dev/null +++ b/internal/cli/cli_paths.go @@ -0,0 +1,47 @@ +package cli + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +// readPasswordFile reads a password from a file, trimming trailing newlines. +// Returns an error when the file cannot be read or the result is empty. +// Used by both migrate-kdbx and the KeePass backend initialisation +// (cli_backend.go → readKeepassPassphrase). +func readPasswordFile(path string) (string, error) { + data, err := os.ReadFile(path) + if err != nil { + return "", fmt.Errorf("reading password file %q: %w", path, err) + } + pass := strings.TrimRight(string(data), "\r\n") + if pass == "" { + return "", fmt.Errorf("password file %q is empty", path) + } + return pass, nil +} + +// resolveHomeDir returns the current user's home directory. +// Falls back to "." when os.UserHomeDir fails so callers always receive a +// non-empty path. +func resolveHomeDir() string { + home, err := os.UserHomeDir() + if err != nil || home == "" { + return "." + } + return home +} + +// expandHome expands a leading "~" or "~/" to the current user's home directory. +// Paths that do not start with "~" are returned unchanged. +func expandHome(path string) string { + if path == "~" { + return resolveHomeDir() + } + if strings.HasPrefix(path, "~/") { + return filepath.Join(resolveHomeDir(), path[2:]) + } + return path +} -- cgit v1.2.3