From c3e8ad67ed56f8ff70c754b61221def79f902d50 Mon Sep 17 00:00:00 2001 From: sid Date: Sun, 26 Apr 2026 08:23:48 -0600 Subject: [PATCH] =?UTF-8?q?complete=20fgj=20=E2=86=92=20fj=20rename:=20env?= =?UTF-8?q?=20vars,=20config=20migration,=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename env vars: FGJ_HOST → FJ_HOST, FGJ_TOKEN → FJ_TOKEN, FGJ_FORCE_TTY → FJ_FORCE_TTY, FGJ_PAGER → FJ_PAGER, FGJ_BINARY_PATH → FJ_BINARY_PATH (all with legacy fallback) - Auto-migrate ~/.config/fgj/ → ~/.config/fj/ on first run - Update man page title, README, CHANGELOG - Update test fixture labels from [FGJ E2E Test] to [FJ E2E Test] --- CHANGELOG.md | 2 +- README.md | 6 ++-- cmd/auth.go | 2 +- cmd/manpages.go | 2 +- cmd/root.go | 48 ++++++++++++++++++++++++++++- internal/config/config.go | 13 ++++++-- internal/iostreams/iostreams.go | 15 +++++---- tests/functional/fixtures.go | 5 ++- tests/functional/functional_test.go | 34 ++++++++++---------- 9 files changed, 94 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 466029f..0565379 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -189,7 +189,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 #### Authentication - `fj auth login` - Interactive authentication with Forgejo instances - `fj auth status` - Check authentication status -- Environment variable support (`FGJ_HOST`, `FGJ_TOKEN`) +- Environment variable support (`FJ_HOST`, `FJ_TOKEN`) #### Development - Comprehensive unit test suite diff --git a/README.md b/README.md index 1936ff9..656d90b 100644 --- a/README.md +++ b/README.md @@ -494,12 +494,12 @@ hosts: ### Environment Variables -- `FGJ_HOST`: Override the default instance (auto-detected from git remote if not set) -- `FGJ_TOKEN`: Provide authentication token +- `FJ_HOST`: Override the default instance (auto-detected from git remote if not set) +- `FJ_TOKEN`: Provide authentication token Hostname is resolved in this priority order: 1. Command-specific flags (e.g., `--hostname`) -2. `FGJ_HOST` environment variable +2. `FJ_HOST` environment variable 3. Auto-detected from git remote URL 4. `match_dirs` lookup (longest prefix match against current directory) 5. Default to `codeberg.org` diff --git a/cmd/auth.go b/cmd/auth.go index 8a3c2d6..16034f0 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -188,7 +188,7 @@ func resolveAuthHostname(cfg *config.Config, hostname string) (string, error) { hostname = viper.GetString("hostname") } if hostname == "" { - hostname = os.Getenv("FGJ_HOST") + hostname = config.EnvWithFallback("FJ_HOST", "FGJ_HOST") } if hostname == "" { hostname = getDetectedHost() diff --git a/cmd/manpages.go b/cmd/manpages.go index 5422041..b1124f1 100644 --- a/cmd/manpages.go +++ b/cmd/manpages.go @@ -29,7 +29,7 @@ var manpagesCmd = &cobra.Command{ } header := &doc.GenManHeader{ - Title: "FGJ", + Title: "FJ", Section: "1", } diff --git a/cmd/root.go b/cmd/root.go index 2cf4156..ed3d6be 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -2,7 +2,9 @@ package cmd import ( "fmt" + "io" "os" + "path/filepath" "strconv" "strings" @@ -52,6 +54,18 @@ func initConfig() { } configDir := home + "/.config/fj" + legacyDir := home + "/.config/fgj" + + // Migrate from ~/.config/fgj/ if the new dir doesn't exist yet. + if _, err := os.Stat(configDir); os.IsNotExist(err) { + if info, err := os.Stat(legacyDir); err == nil && info.IsDir() { + if copyErr := migrateConfigDir(legacyDir, configDir); copyErr == nil { + fmt.Fprintln(ios.ErrOut, "notice: migrated config from ~/.config/fgj/ to ~/.config/fj/") + fmt.Fprintln(ios.ErrOut, " you can remove ~/.config/fgj/ when ready") + } + } + } + _ = os.MkdirAll(configDir, 0755) viper.AddConfigPath(configDir) @@ -60,7 +74,7 @@ func initConfig() { } viper.AutomaticEnv() - viper.SetEnvPrefix("FGJ") + viper.SetEnvPrefix("FJ") _ = viper.ReadInConfig() } @@ -127,3 +141,35 @@ func parseIssueArg(arg string) (int64, error) { } return strconv.ParseInt(arg, 10, 64) } + +// migrateConfigDir copies all files from src to dst (one level, no subdirs). +func migrateConfigDir(src, dst string) error { + if err := os.MkdirAll(dst, 0755); err != nil { + return err + } + entries, err := os.ReadDir(src) + if err != nil { + return err + } + for _, e := range entries { + if e.IsDir() { + continue + } + in, err := os.Open(filepath.Join(src, e.Name())) + if err != nil { + return err + } + out, err := os.OpenFile(filepath.Join(dst, e.Name()), os.O_CREATE|os.O_WRONLY, 0600) + if err != nil { + in.Close() + return err + } + _, err = io.Copy(out, in) + in.Close() + out.Close() + if err != nil { + return err + } + } + return nil +} diff --git a/internal/config/config.go b/internal/config/config.go index 6fac829..e0abfca 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -131,7 +131,7 @@ func (c *Config) SaveToPath(path string) error { // Priority order: // 1. Explicitly provided hostname parameter // 2. CLI flag (--hostname) -// 3. Environment variable (FGJ_HOST) +// 3. Environment variable (FJ_HOST, with FGJ_HOST fallback) // 4. Auto-detected hostname from git remote // 5. match_dirs lookup (longest prefix match) // 6. Default to codeberg.org @@ -141,7 +141,7 @@ func (c *Config) GetHost(hostname string, detectedHost string, cwd string) (Host } if hostname == "" { - hostname = os.Getenv("FGJ_HOST") + hostname = EnvWithFallback("FJ_HOST", "FGJ_HOST") } if hostname == "" { @@ -228,6 +228,15 @@ func (c *Config) ResolveHostByPath(cwd string) string { } // expandHome replaces a leading ~ with the user's home directory. +// EnvWithFallback returns the value of the primary env var, falling back to +// the legacy name if the primary is unset. This eases the FGJ_ → FJ_ rename. +func EnvWithFallback(primary, legacy string) string { + if v := os.Getenv(primary); v != "" { + return v + } + return os.Getenv(legacy) +} + func expandHome(path string) string { if path == "~" || strings.HasPrefix(path, "~/") { home, err := os.UserHomeDir() diff --git a/internal/iostreams/iostreams.go b/internal/iostreams/iostreams.go index 88561ad..8e6528b 100644 --- a/internal/iostreams/iostreams.go +++ b/internal/iostreams/iostreams.go @@ -37,10 +37,10 @@ type IOStreams struct { } // New creates an IOStreams wired to the real os.Stdin, os.Stdout, and os.Stderr, -// with TTY status auto-detected. Setting FGJ_FORCE_TTY=1 forces all streams to -// be treated as TTYs. +// with TTY status auto-detected. Setting FJ_FORCE_TTY=1 (or legacy FGJ_FORCE_TTY=1) +// forces all streams to be treated as TTYs. func New() *IOStreams { - forceTTY := os.Getenv("FGJ_FORCE_TTY") != "" + forceTTY := os.Getenv("FJ_FORCE_TTY") != "" || os.Getenv("FGJ_FORCE_TTY") != "" stdinTTY := forceTTY || (isTerminal(os.Stdin.Fd())) stdoutTTY := forceTTY || (isTerminal(os.Stdout.Fd())) @@ -118,14 +118,17 @@ func (s *IOStreams) ColorScheme() *ColorScheme { } // StartPager starts an external pager process and redirects Out to its stdin. -// It checks FGJ_PAGER, then PAGER, then defaults to "less". If LESS is not -// already set, it is set to "FRX" for a good default experience. +// It checks FJ_PAGER (or legacy FGJ_PAGER), then PAGER, then defaults to "less". +// If LESS is not already set, it is set to "FRX" for a good default experience. func (s *IOStreams) StartPager() error { if !s.isStdoutTTY { return nil } - pagerCmd := os.Getenv("FGJ_PAGER") + pagerCmd := os.Getenv("FJ_PAGER") + if pagerCmd == "" { + pagerCmd = os.Getenv("FGJ_PAGER") + } if pagerCmd == "" { pagerCmd = os.Getenv("PAGER") } diff --git a/tests/functional/fixtures.go b/tests/functional/fixtures.go index b0ec8a9..ec6b72d 100644 --- a/tests/functional/fixtures.go +++ b/tests/functional/fixtures.go @@ -230,7 +230,10 @@ func (env *TestEnv) CleanupRepo(owner, repoName string) { // GetBinaryPath returns the path to the built fj binary func (env *TestEnv) GetBinaryPath() string { - binaryPath := os.Getenv("FGJ_BINARY_PATH") + binaryPath := os.Getenv("FJ_BINARY_PATH") + if binaryPath == "" { + binaryPath = os.Getenv("FGJ_BINARY_PATH") + } if binaryPath == "" { // Look for the binary in common locations candidates := []string{ diff --git a/tests/functional/functional_test.go b/tests/functional/functional_test.go index a838d9e..740cde3 100644 --- a/tests/functional/functional_test.go +++ b/tests/functional/functional_test.go @@ -86,7 +86,7 @@ func TestCLIIssueList(t *testing.T) { env := NewTestEnv(t) // Create a test issue so the list is not empty - issueNum := env.CreateTestIssue("[FGJ E2E Test] Issue List", "For issue list test") + issueNum := env.CreateTestIssue("[FJ E2E Test] Issue List", "For issue list test") defer env.CleanupIssue(issueNum) result := env.RunCLI( @@ -109,7 +109,7 @@ func TestCLIIssueList(t *testing.T) { func TestCLIIssueListJSON(t *testing.T) { env := NewTestEnv(t) - issueNum := env.CreateTestIssue("[FGJ E2E Test] JSON List", "For JSON output test") + issueNum := env.CreateTestIssue("[FJ E2E Test] JSON List", "For JSON output test") defer env.CleanupIssue(issueNum) result := env.RunCLI( @@ -137,7 +137,7 @@ func TestCLIIssueListJSON(t *testing.T) { func TestCLIIssueView(t *testing.T) { env := NewTestEnv(t) - issueNum := env.CreateTestIssue("[FGJ E2E Test] View Test", "Testing issue view") + issueNum := env.CreateTestIssue("[FJ E2E Test] View Test", "Testing issue view") defer env.CleanupIssue(issueNum) result := env.RunCLI( @@ -160,7 +160,7 @@ func TestCLIIssueView(t *testing.T) { func TestCLIIssueViewJSON(t *testing.T) { env := NewTestEnv(t) - issueNum := env.CreateTestIssue("[FGJ E2E Test] JSON View", "Testing JSON view") + issueNum := env.CreateTestIssue("[FJ E2E Test] JSON View", "Testing JSON view") defer env.CleanupIssue(issueNum) result := env.RunCLI( @@ -198,7 +198,7 @@ func TestCLIIssueCreate(t *testing.T) { "--hostname", env.Hostname, "-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName), "issue", "create", - "-t", "[FGJ E2E Test] CLI Created Issue", + "-t", "[FJ E2E Test] CLI Created Issue", "-b", "Created directly via fj CLI", ) @@ -229,7 +229,7 @@ func TestCLIIssueCreateWithLabels(t *testing.T) { "--hostname", env.Hostname, "-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName), "issue", "create", - "-t", "[FGJ E2E Test] Issue with Labels", + "-t", "[FJ E2E Test] Issue with Labels", "-b", "This issue was created with labels", "-l", "bug", "-l", "enhancement", @@ -275,7 +275,7 @@ func TestCLIIssueCreateWithLabels(t *testing.T) { func TestCLIIssueComment(t *testing.T) { env := NewTestEnv(t) - issueNum := env.CreateTestIssue("[FGJ E2E Test] Comment Test", "Testing comment via CLI") + issueNum := env.CreateTestIssue("[FJ E2E Test] Comment Test", "Testing comment via CLI") defer env.CleanupIssue(issueNum) result := env.RunCLI( @@ -313,7 +313,7 @@ func TestCLIIssueComment(t *testing.T) { func TestCLIIssueClose(t *testing.T) { env := NewTestEnv(t) - issueNum := env.CreateTestIssue("[FGJ E2E Test] Close Test", "Will be closed via CLI") + issueNum := env.CreateTestIssue("[FJ E2E Test] Close Test", "Will be closed via CLI") result := env.RunCLI( "--hostname", env.Hostname, @@ -341,7 +341,7 @@ func TestCLIIssueClose(t *testing.T) { func TestCLIIssueCloseWithComment(t *testing.T) { env := NewTestEnv(t) - issueNum := env.CreateTestIssue("[FGJ E2E Test] Close with comment", "Will be closed with a comment") + issueNum := env.CreateTestIssue("[FJ E2E Test] Close with comment", "Will be closed with a comment") commentText := "Fixed in v2.0 - closing via functional test" @@ -389,7 +389,7 @@ func TestCLIIssueCloseWithComment(t *testing.T) { func TestCLIIssueEditTitle(t *testing.T) { env := NewTestEnv(t) - issueNum := env.CreateTestIssue("[FGJ E2E Test] Original Title", "Will be edited") + issueNum := env.CreateTestIssue("[FJ E2E Test] Original Title", "Will be edited") defer env.CleanupIssue(issueNum) result := env.RunCLI( @@ -397,7 +397,7 @@ func TestCLIIssueEditTitle(t *testing.T) { "-R", fmt.Sprintf("%s/%s", env.Owner, env.RepoName), "issue", "edit", fmt.Sprintf("%d", issueNum), - "-t", "[FGJ E2E Test] Updated Title", + "-t", "[FJ E2E Test] Updated Title", ) if result.ExitCode != 0 { @@ -409,7 +409,7 @@ func TestCLIIssueEditTitle(t *testing.T) { t.Fatalf("failed to get issue: %v", err) } - if issue.Title != "[FGJ E2E Test] Updated Title" { + if issue.Title != "[FJ E2E Test] Updated Title" { t.Fatalf("expected updated title, got '%s'", issue.Title) } @@ -421,7 +421,7 @@ func TestCLIIssueEditAddLabels(t *testing.T) { env.EnsureTestLabels() - issueNum := env.CreateTestIssue("[FGJ E2E Test] Add Labels", "Will have labels added") + issueNum := env.CreateTestIssue("[FJ E2E Test] Add Labels", "Will have labels added") defer env.CleanupIssue(issueNum) result := env.RunCLI( @@ -464,7 +464,7 @@ func TestCLIIssueEditRemoveLabels(t *testing.T) { env.EnsureTestLabels() issue, _, err := env.Client.CreateIssue(env.Owner, env.RepoName, gitea.CreateIssueOption{ - Title: "[FGJ E2E Test] Remove Labels", + Title: "[FJ E2E Test] Remove Labels", Body: "Will have labels removed", }) if err != nil { @@ -616,7 +616,7 @@ func TestCLIPRComment(t *testing.T) { env := NewTestEnv(t) // PRs share the comment API with issues - issueNum := env.CreateTestIssue("[FGJ E2E Test] PR Comment Test", "Testing pr comment command") + issueNum := env.CreateTestIssue("[FJ E2E Test] PR Comment Test", "Testing pr comment command") defer env.CleanupIssue(issueNum) result := env.RunCLI( @@ -800,7 +800,7 @@ func TestCLIReleaseCreateUploadDelete(t *testing.T) { env := NewTestEnv(t) tag := fmt.Sprintf("fj-test-%d", time.Now().UnixNano()) - title := "FGJ CLI Release Test" + title := "FJ CLI Release Test" notes := "Release created by functional tests" tmpDir := t.TempDir() @@ -1161,7 +1161,7 @@ func TestCLIAPIPostAndDelete(t *testing.T) { "--hostname", env.Hostname, "api", endpoint, "-X", "POST", - "-f", "title=[FGJ E2E Test] API Post Test", + "-f", "title=[FJ E2E Test] API Post Test", "-f", "body=Created via fj api command", )