On this page
Check command to query package name availability across npm, PyPI, the Go module proxy (pkg.go.dev), and GitHub repository namespaces.
#rlsbl.commands.check
#rlsbl.commands.check
Check command to query package name availability across npm, PyPI, the Go module proxy (pkg.go.dev), and GitHub repository namespaces.
#_request_with_backoff
def _request_with_backoff(url, timeout=5, max_retries=3, headers=None)Wrap urllib.request.urlopen with retry logic for HTTP 429 responses.
On HTTP 429 (Too Many Requests): reads the Retry-After header (seconds). If present, sleeps that long. If absent, uses exponential backoff starting at 2 seconds (2, 4, 8, ...).
On other HTTP errors or non-HTTP errors (URLError, timeout): raises immediately without retrying.
Returns the response object on success, or raises the last exception after exhausting retries.
#_ultranormalize
def _ultranormalize(name)Ultranormalize a package name for typosquatting detection.
Strips all separators (-, _, .), replaces visually ambiguous characters (l, L, i, I -> 1; o, O -> 0), and lowercases the result.
#_generate_ultranorm_variants
def _generate_ultranorm_variants(name)Generate name variants that share the same ultranormalized form.
Starting from the PEP 503 normalized form (lowercase, separators normalized), produces all combinations of ambiguous character substitutions: l <-> 1, o <-> 0, i <-> 1 Returns (variants, capped) where variants is a list of up to 64 variants (excluding the original name) and capped is True when the total combination count exceeded the cap.
#_search_npm_similar
def _search_npm_similar(name)Search the npm registry for packages with conflicting monikers.
Queries the npm search API for packages similar to name, then compares each result's normalized moniker against the candidate's. Returns a list of original package names that conflict.
Raises on failure (network, timeout). The caller is responsible for handling the exception appropriately.
#check_npm_availability
def check_npm_availability(name)Check if an npm package name is available.
Returns {"status": "available"|"taken"|"error", "message"?: str}. Distinguishes 404 (truly available) from network/other errors.
#get_npm_variants
def get_npm_variants(name)Generate common npm name variants for similarity checking.
npm's moniker collision algorithm strips all -, ., and _ characters and lowercases before comparing. We generate:
- All separator-swap variants (replace every separator with each of -._)
- The fully stripped form
- Insertion variants when the name has no separators (insert each of -._
at every interior position so we can detect existing hyphenated packages that would collide)
#check_pypi_availability
def check_pypi_availability(name)Check if a PyPI package name is available.
Uses the Simple API (PEP 503) which correctly returns 200 for registered packages even if they have no releases (unlike the JSON API which 404s).
Returns {"status": "available"|"taken"|"error", "message"?: str}. Distinguishes 404 (truly available) from network/other errors.
#get_pypi_variants
def get_pypi_variants(name)Generate common PyPI name variants for similarity checking.
#check_go_availability
def check_go_availability(name)Check if a Go module path exists on pkg.go.dev.
Returns {"status": "not_found"|"exists"|"error", "message"?: str, "note"?: str}.
Go modules use repository paths (e.g. github.com/user/repo), not a flat claimable namespace, so we report "not found" / "exists" rather than the "available" / "taken" language used for npm and PyPI.
#check_github_availability
def check_github_availability(name)Check if a repository name exists on GitHub.
Searches the GitHub API for repositories with the given name. Returns {"status": "available"|"exists"|"error", "count": int, ...}.
#_check_variants
def _check_variants(name, check_fn, get_variants_fn, delay_ms=0)Check name variants for similarity using the given availability checker.
When delay_ms > 0, bypasses the thread pool and checks variants sequentially with time.sleep(delay_ms / 1000) between checks (no delay before the first check). This avoids triggering registry rate limits when many variants are generated.
Returns a list of variant names that are taken/exist.
#_add_structured_conflicts
def _add_structured_conflicts(result, names, rule)Append {"name": ..., "rule": ...} objects to the unified conflict field.
This is the canonical machine-readable surface: every collision mechanism (npm moniker, pypi separator, pypi ultranorm, stdlib) folds its conflicts into result["structured_conflicts"] via this helper. The list is re-sorted by (name, rule) after each addition, so the final ordering is deterministic regardless of attach-site order — and a name that collides through two mechanisms at once contributes two distinct objects.
#_enumerate_conflicts
def _enumerate_conflicts(conflicts)Render a conflict list as a comma-separated, single-quoted enumeration.
Example: ["foo-bar", "foo.bar"] -> "'foo-bar', 'foo.bar'".
#_classify_variant_collisions
def _classify_variant_collisions(name, taken_variants, registry)Classify taken variants as hard normalization collisions or soft similar names.
Hard collisions are names the registry would reject because they normalize identically to the candidate. Soft similar names merely look alike but are distinct after normalization.
Returns (hard_collisions, soft_similar).
#_check_stdlib_collision
def _check_stdlib_collision(name)Check if a name collides with a Python standard library module.
PEP 503 normalizes both the candidate and each stdlib name, then compares. Returns the stdlib module name on collision, or None.
#_check_single_name
def _check_single_name(name, registry, delay_ms=0)Check a single name on a given registry, returning a structured result.
When delay_ms > 0, variant checks use sequential execution with a delay between requests instead of concurrent threads.
Returns a dict with keys: - name: the package name checked - registry: which registry was checked - status: "available", "taken", "exists", "not_found", or "error" - variants: list of similar names that are taken (npm/pypi only) - reason: why the name is taken/unavailable, or None if available/error. Values: "registered", "stdlib", "moniker", "normalized", "ultranorm" (set by _apply_ultranorm_check), or None. - error: error message if status is "error" (absent otherwise) - note: informational note (go/github only, absent otherwise) - github_count: number of GitHub repos (only when registry is "github") - conflicts: full list of colliding package names (npm moniker collisions only; the machine-readable form of the enumerated note) - conflict_rule: the normalization rule that makes the conflicts collide (accompanies conflicts)
#_format_single_result
def _format_single_result(result)Print the verbose output for a single name check result.
Returns an exit code: 0 = available, 1 = taken/collision, 2 = error.
When status is "error", prints the error message and returns 2 immediately, skipping variant/GitHub output (same behavior as the old sys.exit(1) calls).
#_format_table_row
def _format_table_row(result)Return a compact one-line dict suitable for table rendering.
Keys: name, status. The status is a short human-readable string.
#_apply_ultranorm_check
def _apply_ultranorm_check(result, registry, delay_ms)Apply ultranormalization variant checking to a result dict (in-place).
Always runs for PyPI when the name was initially available. Checks each generated variant against PyPI Simple API, applying a delay between requests. Sets ultranorm_conflicts (list of taken variant names) on the result.
#_result_exit_code
def _result_exit_code(result)Compute the exit code for a single check result without printing.
Mirrors the codes the human formatters return: 2 = error, 1 = taken/exists, 0 = available/not_found.
#_result_to_json
def _result_to_json(result, exit_code)Project a check result dict onto the stable JSON surface for one name+target.
Carries the identity (name, target), status, reason, the unified structured_conflicts field (from the collision mechanisms), the human rule sentences for the tokens present, and the exit-relevant code. Optional keys (note, error, github_count) appear only when set.
#run_cmd
def run_cmd(registry, args, flags)Check package name availability for one registry.
Checks one or more names on registry, warning about similar names. Returns (exit_code, payload) where payload is a list of per-name JSON objects (one per checked name). The caller owns process exit and aggregation across registries — this function never calls sys.exit.
Human output is printed here (byte-identical to prior behavior) unless flags["json"] is set, in which case nothing is printed and the caller renders the accumulated payloads.