From cee6f499fc71cb7e4266e14547f1102ef14fcc79 Mon Sep 17 00:00:00 2001 From: Jack Ling <34231795+lingcoder@users.noreply.github.com> Date: Wed, 21 Jan 2026 19:15:42 +0800 Subject: [PATCH] fix(cmd/gf): fix gf gen enums output path error when using relative path (#4636) ## Summary - Fix `gf gen enums` output file created at wrong location when using relative path - Output was incorrectly relative to source directory instead of current working directory - Add `defer gfile.Chdir(originPwd)` to restore original working directory ## Root Cause The code calls `gfile.Chdir(realPath)` to change to source directory before `gfile.PutContents(in.Path, ...)`, causing relative output path to be resolved relative to source directory. ## Solution - Convert output path to absolute using `gfile.Abs()` before `Chdir` - Restore original working directory with `defer` (following `genpb.go` pattern) ## Test Cases - `Test_Gen_Enums_Issue4387_RelativePath` - standard project with relative path - `Test_Gen_Enums_AbsolutePath` - absolute path (should work as before) - `Test_Gen_Enums_Issue4387_Monorepo` - monorepo mode (`cd app/xxx && gf gen enums`) Closes #4387 --- .../internal/cmd/cmd_z_unit_gen_enums_test.go | 158 ++++++++++++++++++ cmd/gf/internal/cmd/genenums/genenums.go | 13 +- .../cmd/testdata/issue/4387/api/types.go | 16 ++ .../internal/cmd/testdata/issue/4387/go.mod | 3 + 4 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 cmd/gf/internal/cmd/cmd_z_unit_gen_enums_test.go create mode 100644 cmd/gf/internal/cmd/testdata/issue/4387/api/types.go create mode 100644 cmd/gf/internal/cmd/testdata/issue/4387/go.mod diff --git a/cmd/gf/internal/cmd/cmd_z_unit_gen_enums_test.go b/cmd/gf/internal/cmd/cmd_z_unit_gen_enums_test.go new file mode 100644 index 000000000..1cee880a7 --- /dev/null +++ b/cmd/gf/internal/cmd/cmd_z_unit_gen_enums_test.go @@ -0,0 +1,158 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package cmd + +import ( + "path/filepath" + "testing" + + "github.com/gogf/gf/v2/os/gfile" + "github.com/gogf/gf/v2/test/gtest" + "github.com/gogf/gf/v2/util/guid" + "github.com/gogf/gf/v2/util/gutil" + + "github.com/gogf/gf/cmd/gf/v2/internal/cmd/genenums" +) + +// https://github.com/gogf/gf/issues/4387 +// Test that the output path is relative to the original working directory, +// not the source directory after Chdir. +func Test_Gen_Enums_Issue4387_RelativePath(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + // Create temp directory to simulate user's project + tempPath = gfile.Temp(guid.S()) + // Copy testdata to temp directory + srcTestData = gtest.DataPath("issue", "4387") + ) + + // Setup: create temp project structure + err := gfile.CopyDir(srcTestData, tempPath) + t.AssertNil(err) + defer gfile.Remove(tempPath) + + // Save original working directory + originalWd := gfile.Pwd() + + // Change to temp directory (simulate user being in project root) + err = gfile.Chdir(tempPath) + t.AssertNil(err) + defer gfile.Chdir(originalWd) // Restore original working directory + + // Run gen enums with relative paths + var ( + srcFolder = "api" + outputPath = filepath.FromSlash("internal/packed/packed_enums.go") + in = genenums.CGenEnumsInput{ + Src: srcFolder, + Path: outputPath, + } + ) + err = gutil.FillStructWithDefault(&in) + t.AssertNil(err) + + _, err = genenums.CGenEnums{}.Enums(ctx, in) + t.AssertNil(err) + + // Expected: file should be created at tempPath/internal/packed/packed_enums.go + expectedPath := filepath.Join(tempPath, "internal", "packed", "packed_enums.go") + // Bug: file is created at tempPath/api/internal/packed/packed_enums.go + wrongPath := filepath.Join(tempPath, "api", "internal", "packed", "packed_enums.go") + + // Assert the file is at the expected location + t.Assert(gfile.Exists(expectedPath), true) + // Assert the file is NOT at the wrong location + t.Assert(gfile.Exists(wrongPath), false) + }) +} + +// Test gen enums with absolute output path (should work correctly) +func Test_Gen_Enums_AbsolutePath(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + tempPath = gfile.Temp(guid.S()) + srcTestData = gtest.DataPath("issue", "4387") + ) + + err := gfile.CopyDir(srcTestData, tempPath) + t.AssertNil(err) + defer gfile.Remove(tempPath) + + originalWd := gfile.Pwd() + err = gfile.Chdir(tempPath) + t.AssertNil(err) + defer gfile.Chdir(originalWd) + + // Use absolute path for output + var ( + srcFolder = "api" + outputPath = filepath.Join(tempPath, "internal", "packed", "packed_enums.go") + in = genenums.CGenEnumsInput{ + Src: srcFolder, + Path: outputPath, + } + ) + err = gutil.FillStructWithDefault(&in) + t.AssertNil(err) + + _, err = genenums.CGenEnums{}.Enums(ctx, in) + t.AssertNil(err) + + // Assert the file exists at absolute path + t.Assert(gfile.Exists(outputPath), true) + }) +} + +// Test gen enums in monorepo mode (cd app/xxx/ then run command) +func Test_Gen_Enums_Issue4387_Monorepo(t *testing.T) { + gtest.C(t, func(t *gtest.T) { + var ( + // Simulate monorepo structure + tempPath = gfile.Temp(guid.S()) + srcTestData = gtest.DataPath("issue", "4387") + // app/myapp is the subdirectory in monorepo + appPath = filepath.Join(tempPath, "app", "myapp") + ) + + // Create monorepo structure: tempPath/app/myapp/api/... + err := gfile.Mkdir(appPath) + t.AssertNil(err) + // Copy testdata into app/myapp + err = gfile.CopyDir(srcTestData, appPath) + t.AssertNil(err) + defer gfile.Remove(tempPath) + + originalWd := gfile.Pwd() + + // cd app/myapp (simulate user in monorepo subdirectory) + err = gfile.Chdir(appPath) + t.AssertNil(err) + defer gfile.Chdir(originalWd) + + var ( + srcFolder = "api" + outputPath = filepath.FromSlash("internal/packed/packed_enums.go") + in = genenums.CGenEnumsInput{ + Src: srcFolder, + Path: outputPath, + } + ) + err = gutil.FillStructWithDefault(&in) + t.AssertNil(err) + + _, err = genenums.CGenEnums{}.Enums(ctx, in) + t.AssertNil(err) + + // Expected: file at app/myapp/internal/packed/packed_enums.go + expectedPath := filepath.Join(appPath, "internal", "packed", "packed_enums.go") + // Bug: file at app/myapp/api/internal/packed/packed_enums.go + wrongPath := filepath.Join(appPath, "api", "internal", "packed", "packed_enums.go") + + t.Assert(gfile.Exists(expectedPath), true) + t.Assert(gfile.Exists(wrongPath), false) + }) +} diff --git a/cmd/gf/internal/cmd/genenums/genenums.go b/cmd/gf/internal/cmd/genenums/genenums.go index a214072c2..27ff362e8 100644 --- a/cmd/gf/internal/cmd/genenums/genenums.go +++ b/cmd/gf/internal/cmd/genenums/genenums.go @@ -55,6 +55,13 @@ func (c CGenEnums) Enums(ctx context.Context, in CGenEnumsInput) (out *CGenEnums if realPath == "" { mlog.Fatalf(`source folder path "%s" does not exist`, in.Src) } + // Convert output path to absolute before Chdir, so it remains correct after directory change. + // See: https://github.com/gogf/gf/issues/4387 + outputPath := gfile.Abs(in.Path) + + originPwd := gfile.Pwd() + defer gfile.Chdir(originPwd) + err = gfile.Chdir(realPath) if err != nil { mlog.Fatal(err) @@ -72,14 +79,14 @@ func (c CGenEnums) Enums(ctx context.Context, in CGenEnumsInput) (out *CGenEnums p := NewEnumsParser(in.Prefixes) p.ParsePackages(pkgs) var enumsContent = gstr.ReplaceByMap(consts.TemplateGenEnums, g.MapStrStr{ - "{PackageName}": gfile.Basename(gfile.Dir(in.Path)), + "{PackageName}": gfile.Basename(gfile.Dir(outputPath)), "{EnumsJson}": "`" + p.Export() + "`", }) enumsContent = gstr.Trim(enumsContent) - if err = gfile.PutContents(in.Path, enumsContent); err != nil { + if err = gfile.PutContents(outputPath, enumsContent); err != nil { return } - mlog.Printf(`generated enums go file: %s`, in.Path) + mlog.Printf(`generated enums go file: %s`, outputPath) mlog.Print("done!") return } diff --git a/cmd/gf/internal/cmd/testdata/issue/4387/api/types.go b/cmd/gf/internal/cmd/testdata/issue/4387/api/types.go new file mode 100644 index 000000000..9af24e033 --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/issue/4387/api/types.go @@ -0,0 +1,16 @@ +// Copyright GoFrame gf Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package api + +// Status is a sample enum type for testing. +type Status int + +const ( + StatusPending Status = iota + StatusActive + StatusDone +) diff --git a/cmd/gf/internal/cmd/testdata/issue/4387/go.mod b/cmd/gf/internal/cmd/testdata/issue/4387/go.mod new file mode 100644 index 000000000..f2ecd4c8d --- /dev/null +++ b/cmd/gf/internal/cmd/testdata/issue/4387/go.mod @@ -0,0 +1,3 @@ +module github.com/gogf/gf/cmd/gf/v2/internal/cmd/testdata/issue/4387 + +go 1.20