From d1d2f8c5c542d3ff5484e94b729e66116bad6a67 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Fri, 11 Oct 2024 21:28:27 -0500 Subject: [PATCH] feat: support XDG standards on new installs (#230) --- docs/schema/schema.scaffoldrc.ts | 9 ++ internal/appdirs/appdirs.go | 156 +++++++++++++++++++++++++++++++ main.go | 26 +++--- 3 files changed, 180 insertions(+), 11 deletions(-) create mode 100644 internal/appdirs/appdirs.go diff --git a/docs/schema/schema.scaffoldrc.ts b/docs/schema/schema.scaffoldrc.ts index 1a221ba..de058b3 100644 --- a/docs/schema/schema.scaffoldrc.ts +++ b/docs/schema/schema.scaffoldrc.ts @@ -10,6 +10,15 @@ export interface Schema { * `prompt`. * */ run_hooks?: "always" | "never" | "prompt"; + /** + * the log level for the scaffold. The default is `warn`. + * */ + log_level?: "debug" | "info" | "warn" | "error"; + /** + * the log file path for the scaffold. The default is none. When provided without a `/` prefix + * the path is relative to scaffoldrc file. + * */ + log_file?: string; }; /** * The aliases section allows you to define key/value pairs as shortcuts for a scaffold path. diff --git a/internal/appdirs/appdirs.go b/internal/appdirs/appdirs.go new file mode 100644 index 0000000..63b0484 --- /dev/null +++ b/internal/appdirs/appdirs.go @@ -0,0 +1,156 @@ +// Package appdirs provides a cross-platform way to find application directories for +// the configuration, cache, and any other application-specific directories. +// +// This package largely exists because of the legacy behavior of the scaffold CLI where +// we want to preserve user locations for configuration if it exists, but move forward +// to use the XDG Base Directory Specification for new installations. +package appdirs + +import ( + "os" + "path/filepath" + + "github.com/rs/zerolog/log" +) + +const ( + RCFilename = "scaffoldrc.yml" +) + +// RCFilepath returns the path to the scaffold configuration file. This is done +// by: +// +// 1. Checking if the legacy configuration file exists in the user's home directory. +// 2. If it does, return the path to the legacy configuration file. +// 3. If it does not, return the path to the XDG configuration file. +func RCFilepath() string { + legacyFilepath, exists := RCFilepathLegacy() + if exists { + return legacyFilepath + } + + return RCFilepathXDG() +} + +func RCFilepathLegacy() (path string, exists bool) { + fp := homeDir(".scaffold", RCFilename) + _, err := os.Stat(fp) + if err != nil { + return "", false + } + + return fp, true +} + +func RCFilepathXDG() string { + configDir := os.Getenv("XDG_CONFIG_HOME") + if configDir == "" { + home, err := os.UserHomeDir() + if err != nil { + log.Fatal().Err(err).Msg("failed to get home directory") + } + + configDir = filepath.Join(home, ".config") + } + + return filepath.Join(configDir, "scaffold", RCFilename) +} + +// CacheDir returns the path to the scaffold cache directory. This is done by: +// +// 1. Checking if the XDG_DATA_HOME environment variable is set. +// 2. If it is, return the path to the cache directory. +// 3. If it is not, return the path to the cache directory in the user's home directory. +func CacheDir() string { + legacyFilepath, exists := CacheDirLegacy() + if exists { + return legacyFilepath + } + + return CacheDirXDG() +} + +func CacheDirLegacy() (path string, exists bool) { + dir := homeDir(".scaffold/cache") + _, err := os.Stat(dir) + if err != nil { + return "", false + } + + return dir, true +} + +func CacheDirXDG() string { + cacheDir := os.Getenv("XDG_DATA_HOME") + if cacheDir == "" { + home, err := os.UserHomeDir() + if err != nil { + log.Fatal().Err(err).Msg("failed to get home directory") + } + + cacheDir = filepath.Join(home, ".local", "share") + } + + return filepath.Join(cacheDir, "scaffold", "templates") +} + +func homeDir(s ...string) string { + home, err := os.UserHomeDir() + if err != nil { + log.Fatal().Err(err).Msg("failed to get home directory") + } + + return filepath.Join(append([]string{home}, s...)...) +} + +// MigrateLegacyPaths migrates the legacy configuration and cache directories to the +// new XDG Base Directory Specification paths. +func MigrateLegacyPaths() error { + legacyFilepath, exists := RCFilepathLegacy() + if exists { + err := migrateRCFile(legacyFilepath) + if err != nil { + return err + } + } + + legacyDirectory, exists := CacheDirLegacy() + if exists { + err := migrateCacheDir(legacyDirectory) + if err != nil { + return err + } + } + + return nil +} + +func migrateRCFile(legacyFilepath string) error { + xdgFilepath := RCFilepathXDG() + err := os.MkdirAll(filepath.Dir(xdgFilepath), os.ModePerm) + if err != nil { + return err + } + + err = os.Rename(legacyFilepath, xdgFilepath) + if err != nil { + log.Error().Err(err).Msg("failed to migrate legacy configuration file") + return err + } + + log.Info().Str("from", legacyFilepath).Str("to", xdgFilepath).Msg("migrated legacy configuration file") + return nil +} + +func migrateCacheDir(legacyDir string) error { + xdgDir := CacheDirXDG() + + err := os.Rename(legacyDir, xdgDir) + if err != nil { + log.Error().Err(err).Msg("failed to migrate legacy cache directory") + return err + } + + log.Info().Str("from", legacyDir).Str("to", xdgDir).Msg("migrated legacy cache directory") + return nil +} diff --git a/main.go b/main.go index ee1302c..b61ca89 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,7 @@ import ( "github.com/hay-kot/scaffold/app/commands" "github.com/hay-kot/scaffold/app/core/engine" "github.com/hay-kot/scaffold/app/scaffold/scaffoldrc" + "github.com/hay-kot/scaffold/internal/appdirs" "github.com/hay-kot/scaffold/internal/printer" "github.com/hay-kot/scaffold/internal/styles" "github.com/rs/zerolog" @@ -36,15 +37,6 @@ func build() string { return fmt.Sprintf("%s (%s) %s", version, short, date) } -func HomeDir(s ...string) string { - home, err := os.UserHomeDir() - if err != nil { - log.Fatal().Err(err).Msg("failed to get home directory") - } - - return filepath.Join(append([]string{home}, s...)...) -} - func main() { log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.WarnLevel) @@ -61,7 +53,7 @@ func main() { &cli.PathFlag{ Name: "scaffoldrc", Usage: "path to scaffoldrc file", - Value: HomeDir(".scaffold/scaffoldrc.yml"), + Value: appdirs.RCFilepath(), EnvVars: []string{"SCAFFOLDRC"}, }, &cli.StringSliceFlag{ @@ -73,7 +65,7 @@ func main() { &cli.PathFlag{ Name: "cache", Usage: "path to the local scaffold directory default", - Value: HomeDir(".scaffold/cache"), + Value: appdirs.CacheDir(), EnvVars: []string{"SCAFFOLD_CACHE"}, }, &cli.StringFlag{ @@ -386,6 +378,18 @@ func main() { return nil }, }, + { + Name: "migrate", + Action: func(ctx *cli.Context) error { + err := appdirs.MigrateLegacyPaths() + if err != nil { + return err + } + + log.Info().Msg("migrated legacy paths") + return nil + }, + }, }, }, },