Skip to content

feat(registry): Go module support - #126

Open
WayOfThKeyboard wants to merge 2 commits into
neuledge:mainfrom
WayOfThKeyboard:feat/go-module-support
Open

feat(registry): Go module support#126
WayOfThKeyboard wants to merge 2 commits into
neuledge:mainfrom
WayOfThKeyboard:feat/go-module-support

Conversation

@WayOfThKeyboard

Copy link
Copy Markdown

What this adds

go as a registry: version discovery through proxy.golang.org, and the recursive definition walk that Go module paths require.

Three things Go does differently, each measured against the live proxy

Uppercase letters must be escaped as ! + lowercase.

https://proxy.golang.org/github.com/BurntSushi/toml/@v/list   -> 404
https://proxy.golang.org/github.com/!burnt!sushi/toml/@v/list -> 200

Slashes are path separators and have to survive, so encodeURIComponent is the wrong tool here.

The v prefix is stripped before the shared helpers see it. isPrerelease("v1.10.2") is true — it reads the leading letter as a prerelease tag — so every Go version would be silently discarded. compareSemver("v1.10.2", "v1.9.1") is NaN, so sorting breaks too. Stripping it in the fetcher and restoring it with tag_pattern: "v{version}" keeps both shared functions untouched, which is why this PR does not go near them.

/@v/list carries no publish dates. Getting them costs one /@v/<version>.info request per version, so --since filtering is unavailable for Go rather than merely slow. publishedAt is left undefined.

The blocker underneath it

listDefinitions only recursed into directories starting with @, so registry/go/github.com/spf13/cobra.yaml was never loaded — exit code 0, no warning. Every Go module path contains slashes, so this blocked the whole ecosystem rather than one package.

The general recursive walk that replaces the special case is smaller than the case it removes. Scoped npm packages still resolve, because loadDefinition already derives expectedName from the path relative to the manager directory.

Taken end to end before opening this

registry build → 56 sections / 19631 tokens → context addcontext query 'github.com/spf13/cobra' 'persistent flags' returned the correct section. registry/go/github.com/spf13/cobra.yaml is included as the first Go definition.

Tests

Four new. Three on the fetcher — prefix stripping, case escaping, a module with no tagged releases — and one on a definition nested several directories deep.

Each was mutation-checked rather than just watched to pass: dropping the escaping, keeping the v prefix, and removing the recursion each turn exactly the matching test red. 45/45 green, biome clean.

One question for a maintainer

std is not on the module proxy, so the Go standard library would need an unversioned definition like python/python.yaml uses, not a go one. Not included here — it seemed like your call which shape you want.

Adds `go` as a registry: version discovery through proxy.golang.org, plus the
recursive definition walk that Go module paths require.

Three things differ from the existing fetchers, each measured against the live
proxy rather than inferred:

- Uppercase letters must be escaped as "!" + lowercase.
  github.com/BurntSushi/toml/@v/list returns 404; github.com/!burnt!sushi/toml
  returns 200. Slashes are path separators and must survive, so
  encodeURIComponent is the wrong tool.
- The "v" prefix is stripped before the shared helpers see it.
  isPrerelease("v1.10.2") is true, so every Go version would be silently
  discarded, and compareSemver("v1.10.2", "v1.9.1") is NaN, so sorting breaks.
  Definitions restore the prefix with tag_pattern "v{version}", which keeps
  both shared functions untouched.
- /@v/list carries no publish dates. Getting them costs one /@v/<version>.info
  request per version, so --since filtering is unavailable for Go rather than
  merely slow. publishedAt is left undefined.

listDefinitions only recursed into directories starting with "@", so
registry/go/github.com/spf13/cobra.yaml was never loaded — exit 0, no warning.
Every Go module path contains slashes, so this blocked the ecosystem rather
than one package. The general recursive walk that replaces the special case is
smaller than the case it removes, and scoped npm packages still resolve because
loadDefinition already derives expectedName from the path relative to the
manager directory.

registry/go/github.com/spf13/cobra.yaml is included as the first definition. It
was taken end to end before this PR: registry build produced 56 sections /
19631 tokens, context add installed it, and `context query
'github.com/spf13/cobra' 'persistent flags'` returned the right section.

Tests: four new ones. Three cover the fetcher (prefix stripping, case escaping,
a module with no tags) and one covers a definition nested several directories
deep. Each was mutation-checked — dropping the escaping, keeping the v prefix,
and removing the recursion each turn exactly the matching test red. 45/45 green,
biome clean.

One open question for a maintainer: `std` is not on the module proxy, so the Go
standard library would need an unversioned definition like python/python.yaml
rather than a `go` one. Not included here.
@changeset-bot

changeset-bot Bot commented Aug 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: cac38d9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@neuledge/registry Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

moshest commented Aug 31, 2026

Copy link
Copy Markdown
Member

Thanks for this — I reviewed it closely and verified your central claims against the live proxy. The case-escaping is right (.../@v/list 404s, .../!burnt!sushi/toml/@v/list 200s), the v-stripping is right, and making listDefinitions recursive is a genuine fix with no regression: registry list goes 140 → 141 with exactly one added line, every existing definition unchanged. cobra@1.10.2 builds at 56 sections, and I confirmed that's not a wrong docs_path — cobra only has 13 markdown files.

Four things block merge. The first is serious.

1. A 4xx from the Go proxy kills the entire nightly run.

discoverVersions is called at cli.ts:228, outside the try that starts at line 235. And version-check.ts:283 aborts immediately on anything below 500 — so 404, 410 (which Go's proxy protocol uses for retracted modules) and 429 all throw straight out of the loop.

Reproduced with a bogus module plus a valid npm definition after it:

Error: Go module proxy returned 404 for github.com/nonexistent-org-xyz/nope
Node.js v22.22.2 — Exit status 1

The npm definition was never reached and no --- Summary --- printed. Definitions sort by registry/name, and go/github.com/spf13/cobra lands at #2 of 141 — so one proxy hiccup takes down the other 139 packages.

This is our fragility, not yours — discoverVersions was never guarded. I'm fixing that separately so a discovery failure is recorded per-package like a build failure. Flagging it so you know it's handled.

2. --since is silently ignored for Go. fetchGoVersions never sets publishedAt, and the filter is if (sinceDate && v.publishedAt) — so it's a no-op that includes everything, not "unavailable":

registry check github.com/spf13/cobra --since 2   → 1.10.2, 1.9.1, 1.8.1   (1.8.1 is from December)
registry check next --since 2                     → only today's versions

The nightly runs --since 2, so every Go definition re-enumerates its full range forever. The rationale in your description — one .info request per version — doesn't quite hold: dates are only needed for versions surviving dedup, which for cobra is 3, not 27. And /@v/v1.10.2.info does return "Time". Either resolve dates post-dedup, or at minimum console.warn so the gap is loud.

3. +incompatible modules discover zero versions, silently. isPrerelease is /[-+]/, so 25.0.10+incompatible is filtered as a prerelease. A definition for github.com/docker/docker with min_version: 20.10.0 gives (0 versions) and exit 0 — the whole v20.10→v28.x range invisible, no signal. Please warn when versions exist but none match, and note the limitation in registry/README.md.

4. Go submodules resolve the wrong tag and skip forever. Default tag_pattern is v{version}, but a module in a subdirectory tags as config/v1.31.1, not v1.31.1. The clone fails with "Remote branch not found", which matches the missing-ref pattern, so cli.ts:301 treats it as "tag not published yet" and skips silently — every night. That covers aws-sdk-go-v2/*, k8s.io/*, google.golang.org/*. Worth documenting with a worked tag_pattern: "<subdir>/v{version}" example.

Docs need updating. registry/README.md:87-93 still says the versions: shape "only works in npm/, pip/, maven/ and hex/" and that anything else fails with Unsupported registry: — which a contributor will now read while a working go/ definition sits in the tree. Also registry/README.md:75, the directory list in README.md:623, and README.md:664 ("npm, PyPI, and Maven Central"). Note packages/context/README.md is byte-identical and ships to npm, so it needs the same edit.

Two smaller notes: deleting .filter(line => line.startsWith("v")) leaves all 12 tests green, so that guard is untested — a non-version line in the fixture body would fix that. And the module path is interpolated raw at version-check.ts:253 while every other fetcher uses encodeURIComponent; not exploitable here since names come from readdirSync, but a ? or # in a filename would silently retarget the request.

Good contribution — the mechanism is right and the verification in your description held up. Happy to review again once these are in.


Generated by Claude Code

moshest added a commit that referenced this pull request Aug 31, 2026
)

publish-all called discoverVersions outside the try that guards the rest of
the loop, so anything it threw escaped and ended the process. Definitions are
walked in sorted order, so one third-party registry API returning 404, 410 or
429 silently abandoned every package after it and printed no summary at all.
version-check.ts aborts immediately below status 500, so there was no retry
either.

Discovery talks to an external API and fails for reasons unrelated to the
package — a renamed module, a rate limit, an upstream blip. Those now record a
failure for that definition and continue, exactly like a failed build.

Found while reviewing #126, which adds a Go registry and makes this trivially
reachable: go/github.com/spf13/cobra sorts second of 141, so one proxy hiccup
would take down the other 139. The fragility is ours and predates it.

The new tests run the real CLI against a scratch registry using a registry
with no version fetcher, so discovery throws before any network call. Both
were verified load-bearing: they fail with the guard reverted and pass with it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants