Skip to content

workspace

Where is the project root? Nearly every CLI tool needs to answer that before it does anything else. workspace answers it the way git and go do — walk up from a starting directory until a marker file appears.

go get gitlab.com/phpboyscout/go/workspace
ws, err := workspace.DetectFromCWD(afero.NewOsFs(), workspace.DefaultMarkers)
if err != nil {
    return errors.Wrap(err, "not inside a project")
}
fmt.Println("Project root:", ws.Root)   // e.g. /home/user/project
fmt.Println("Detected via:", ws.Marker) // e.g. go.mod

Why

  • The filesystem is injected. Detect and DetectFromCWD both take an afero.Fs, so the walk runs entirely in memory in tests — no temp directories, no touching the real disk. Pass afero.NewOsFs() in production, afero.NewMemMapFs() in tests. See test filesystem walks in memory.
  • Markers are ordered; first match wins. DefaultMarkers is {".gtb/manifest.yaml", "go.mod", ".git"} — a generated-project manifest outranks the Go module root, which outranks the git root. Bring your own set for any ecosystem — see detect other project types.
  • The walk is bounded. It climbs at most DefaultMaxDepth (100) parents before giving up with ErrNotFound, so a stray path can never scan unboundedly. Override with WithMaxDepth. The full mechanism is in the marker walk.
  • No framework weight. The entire dependency graph is afero, cockroachdb/errors, and the standard library — enforced by a depfootprint test, and the reason this is its own module.

What it will not do

Before you build on it: the walk returns the nearest enclosing marker and cannot be asked for the outermost one, it does not resolve symlinks, it never opens the marker file, and every failed search returns the same ErrNotFound whatever caused it. The full list is in what workspace does not do.

Start here

Godoc signatures live on pkg.go.dev.

Further reading

The blog carries a curated route through this subject: Building a command-line tool in Go collects everything written about it, ordered so you can start at the beginning rather than newest-first.

Ask phpbotscout

phpbotscout

He answers questions about the projects over on the Discord, citing the docs where they already cover it, and offering to raise an issue where they don't. Bring a bug, an idea, or a questionable engineering decision.

Join the Discord