From 5716f0f757f152a13395eb5866ade9a0d10eb76b Mon Sep 17 00:00:00 2001 From: AJ ONeal Date: Sat, 28 Feb 2026 10:46:47 -0700 Subject: [PATCH] fix(monorel): guard against nested go.mod in binary paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Any directory on the path from the module root to a binary that contains its own go.mod is a separate module; monorel should not try to manage it. Two cases are now caught with distinct errors: ../other → "outside the module directory" ./cmd/go.mod → "has its own go.mod" (intermediate dir) ./cmd/foo/go.mod → "has its own go.mod" (binary dir itself) Both suggest the correct fix: cd into that directory and run monorel from there. --- tools/monorel/main.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tools/monorel/main.go b/tools/monorel/main.go index f1a6f7e..a58d305 100644 --- a/tools/monorel/main.go +++ b/tools/monorel/main.go @@ -57,6 +57,35 @@ func main() { // 1. Parse binary descriptors from positional args. bins := parseBinaries(args) + // Guard: binary paths must be strictly inside the module directory, and no + // directory along the path (between the module root and the binary itself, + // inclusive) may contain its own go.mod. + // + // Examples that must be rejected: + // ../other (escapes the module root) + // ./cmd/go.mod (intermediate dir is its own module) + // ./cmd/foo/go.mod (binary dir is its own module) + for _, bin := range bins { + if bin.mainPath == "." { + continue // the module root itself — already checked above + } + if strings.HasPrefix(bin.mainPath, "../") { + fatalf("%s is outside the module directory", bin.mainPath) + } + // Walk every directory segment from the first child of the module root + // down to the binary directory. + rel := strings.TrimPrefix(bin.mainPath, "./") + parts := strings.Split(rel, "/") + for i := range parts { + dir := "./" + strings.Join(parts[:i+1], "/") + if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil { + fatalf("%s has its own go.mod — it is a separate module.\n"+ + " Run monorel from that directory instead:\n"+ + " cd %s && monorel .", dir, dir) + } + } + } + // 2. Module prefix relative to the .git root (e.g., "io/transform/gsheet2csv"). // This is also the tag prefix, e.g. "io/transform/gsheet2csv/v1.2.3". prefix := mustRun("git", "rev-parse", "--show-prefix")