Summary
Fixes #61. opensrc path next-auth (and any package whose registry response includes a version with a string-shorthand repository field) failed with Error: error decoding response body. Root cause: NpmRepository was modeled as a single struct, but package.json's repository field is either an object ({ url, directory, ... }) or a plain string shorthand. The npm registry preserves whichever shape was published, so one shorthand entry tanks the whole response.
Concrete reproducer that was failing before this PR:
$ opensrc path next-auth
Error: error decoding response bodynext-auth 3.29.3 and 3.29.7 publish "repository": "https://github.com/nextauthjs/next-auth.git" as a string. Those two entries among the 725-version registry response were enough to fail the whole decode, even though current versions use the object form.
Approach
Model NpmRepository as a #[serde(untagged)] enum with two variants — Object { url, directory } and Shorthand(String) — plus thin url() / directory() accessors so the call site in extract_repo_url stays unchanged in shape. The existing URL normalization in extract_repo_url already handles git+, git+ssh://, and github: prefixes, so it covers the common shorthand URLs without further changes.
Tests
Four new unit tests in packages/opensrc/cli/src/core/registries/npm.rs:
test_npm_repository_decodes_object_form— object shape still decodes.test_npm_repository_decodes_string_shorthand— regression test naming next-auth 3.29.3 / 3.29.7.test_npm_response_decodes_with_mixed_repository_shapes— fullNpmResponsewith a mix of object-form, string-form, and missingrepositoryentries. Mirrors the exact shape that brokeopensrc path next-auth.test_extract_repo_url_handles_string_shorthand—extract_repo_urlcorrectly normalizes a shorthand URL.
cargo test, cargo fmt --check, and cargo clippy --all-targets -- -D warnings all pass locally.
End-to-end verification
Built a local release binary against this branch:
$ ./target/release/opensrc path next-auth
/Users/.../nextauthjs/next-auth/4.24.14
$ ./target/release/opensrc path zod
/Users/.../colinhacks/zod/3.25.76
$ ./target/release/opensrc path @auth/core
/Users/.../nextauthjs/next-auth/0.34.3next-auth now resolves; unrelated packages still resolve.
Notes
- No public API change.
- The
Shorthandvariant intentionally accepts any string and feeds it through the same URL-normalization path as the object form'surl. Edge shorthands like the bare"owner/repo"form aren't expanded here — they were never handled before either, and a follow-up could address them if desired. The next-auth case (a full URL string) works because of the existing normalizer.
Activity
3Ran into the same error decoding response body independently with velite, which surfaces a variant this PR doesn't fully cover yet: the bare owner/repo shorthand.
$ curl -s https://registry.npmjs.org/velite | jq -r '.versions | to_entries[] | select(.value.repository | type == "string") | "\(.key) \(.value.repository)"'
1.0.0-alpha.2 zce/velite
1.0.0-alpha.3 zce/veliteThis is the form the npm docs document as the default shorthand ("repository": "npm/npm" → GitHub). The untagged enum in this PR fixes deserialization for it — which is what poisons the whole packument today — but if a bare-shorthand version is the one being resolved, extract_repo_url passes zce/velite through unchanged (it only normalizes git+ / git:// / ssh:// / github:-prefixed strings), so the resulting clone URL is invalid.
Suggestion: in the normalization path, treat a scheme-less string matching ^[^/@#:]+/[^/@#:]+$ as https://github.com/{s} (npm's documented default host), and add it as a test case alongside the full-URL string form.
Either way, the enum change alone already unblocks both next-auth and velite for normal resolution, since their latest versions publish the object form — thanks for the fix.
(analysis assisted by Claude Code)
@Steel-tech is attempting to deploy a commit to the Vercel Labs Team on Vercel.
A member of the Team first needs to authorize it.