feat(fnode): resolve Five path from fivenode_go's go.mod replace pin
When fnode is invoked from a cwd outside fivenode_go (e.g. a sibling
project like /Users/.../solmade), the legacy fallback
"../../fivedev/five" was resolved against the caller's cwd, pointing
fnode at /Users/fivedev/five which doesn't exist. go mod tidy then
failed with "open /Users/fivedev/five/go.mod: no such file".
New resolution order in fiveRoot():
1. walkUpForModule("five") — same as before, wins inside the
Five source tree.
2. fiveFromFnodeGoMod() — parse the `replace five => <path>`
line in fivenode_go's own go.mod (found via fnodeRoot()),
resolving relative paths against that root.
3. Hardcoded relative fallback (legacy).
Lets sibling apps run `fnode build app/foo.prg ...` from their own
directory without needing to cd into fivenode_go first.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -367,15 +367,69 @@ func goBin() string {
|
||||
return "go"
|
||||
}
|
||||
|
||||
// fiveRoot resolves to Five's source tree (walk up from this binary or
|
||||
// from cwd until a directory with module name "five" is found).
|
||||
// fiveRoot resolves to Five's source tree.
|
||||
//
|
||||
// Order of resolution:
|
||||
// 1. Walk up looking for a go.mod with `module five` (works when
|
||||
// fnode was launched from inside the Five source tree).
|
||||
// 2. Read fnodeRoot's go.mod for a `replace five => <path>` line —
|
||||
// this is the common case when fnode is invoked from somewhere
|
||||
// else (e.g. /Users/.../solmade) with the binary living in
|
||||
// fivenode_go's tree; the replace pin in fivenode_go/go.mod is
|
||||
// the source of truth for where Five lives.
|
||||
// 3. Hard-coded relative fallback, resolved against the current
|
||||
// working directory. Only useful when fnode is run from inside
|
||||
// fivenode_go itself.
|
||||
func fiveRoot() string {
|
||||
if d := walkUpForModule("five"); d != "" {
|
||||
return d
|
||||
}
|
||||
if p := fiveFromFnodeGoMod(); p != "" {
|
||||
return p
|
||||
}
|
||||
return "../../fivedev/five"
|
||||
}
|
||||
|
||||
// fiveFromFnodeGoMod parses fivenode_go's own go.mod for a
|
||||
//
|
||||
// replace five => <path>
|
||||
//
|
||||
// directive and returns the path resolved against fnodeRoot when it's
|
||||
// relative. Empty when not found.
|
||||
func fiveFromFnodeGoMod() string {
|
||||
fr := fnodeRoot()
|
||||
if fr == "" {
|
||||
return ""
|
||||
}
|
||||
data, err := os.ReadFile(filepath.Join(fr, "go.mod"))
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
for _, raw := range strings.Split(string(data), "\n") {
|
||||
line := strings.TrimSpace(raw)
|
||||
if !strings.HasPrefix(line, "replace") {
|
||||
continue
|
||||
}
|
||||
// Match either `replace five => path` or `replace five v0.0.0 => path`.
|
||||
if !strings.Contains(line, " five ") && !strings.Contains(line, " five\t") &&
|
||||
!strings.HasSuffix(strings.TrimSpace(strings.SplitN(line, "=>", 2)[0]), " five") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=>", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
p := strings.TrimSpace(parts[1])
|
||||
if filepath.IsAbs(p) {
|
||||
return p
|
||||
}
|
||||
if abs, err := filepath.Abs(filepath.Join(fr, p)); err == nil {
|
||||
return abs
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// fnodeRoot resolves to fivenode_go's source tree.
|
||||
func fnodeRoot() string {
|
||||
if d := walkUpForModule("fivenode_go"); d != "" {
|
||||
|
||||
Reference in New Issue
Block a user