npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

redirect-trace

v0.2.0

Published

Trace HTTP redirect chains locally.

Downloads

489

Readme

redirect-trace

CI npm

curl -IL shows headers, but it does not explain which redirect dropped a query parameter, changed origin, or introduced a risk. redirect-trace is a local CLI for the searches people actually make: "compare redirect query params" and "why did my OAuth redirect drop the code param". It follows one HTTP(S) chain, explains each hop, and provides a CI-safe verdict without sending your URL to a hosted viewer.

It traces redirects with safe HEAD-then-GET requests, preserves duplicate query keys and value order, percent-decodes keys and values, and treats + as a literal plus (%20 is a space). It flags loops, HTTPS-to-HTTP downgrades, origin changes, method-semantic transitions, and terminal error responses.

Install and run

npx redirect-trace https://example.com
pnpm dlx redirect-trace https://example.com --format markdown
pnpm add --global redirect-trace
redirect-trace https://example.com

Requires Node 18 or newer. npx and pnpm dlx users on a current Node need do nothing. The CLI only makes the requests needed to trace the URL you provide; it has no telemetry or hosted service.

Agent and script use

Use exit status as a CI gate. Output remains on stdout, while request failures are reported on stderr.

redirect-trace "$REDIRECT_URL" --format markdown > redirect-trace.md
case $? in
  0) echo "redirect chain is clean" ;;
  1) echo "redirect chain has observations"; exit 1 ;;
  2) echo "redirect-trace usage or environment failure"; exit 2 ;;
  3) echo "redirect request could not complete"; exit 3 ;;
  4) echo "redirect assertion failed"; exit 4 ;;
esac

For scripts and agents, parse the deterministic JSON model. Timing is omitted unless explicitly requested.

redirect-trace "$REDIRECT_URL" --format json > trace.json
node --input-type=module -e 'import { readFileSync } from "node:fs"; const trace = JSON.parse(readFileSync("trace.json", "utf8")); console.log(trace.flags.map(({ kind }) => kind).join(", ") || "clean");'

Exit-code contract v2

| Code | Meaning | | --- | --- | | 0 | Clean: the chain reached a terminal response with no flags. | | 1 | Flagged: the chain resolved, but one or more risks were found, including a terminal 4xx or 5xx response. | | 2 | Usage or environment failure: arguments or the URL are invalid, Node is unsupported, or the CLI could not start. | | 3 | Transport/request failure: timeout, connection error, the engine hop limit, or a 3xx response without Location. | | 4 | Assertion failure: a supplied chain-shape expectation did not match. |

On a mid-chain transport failure, redirect-trace still renders the partial trace and marks the failed hop.

Exit precedence is usage (2), transport (3), assertion (4), flagged (1), then clean (0). Scripts that previously treated every status above 1 as an infrastructure failure must add an explicit 4 branch for a redirect-contract failure.

Assertions

Use assertions to make a redirect chain a CI gate:

redirect-trace https://old.example.com/docs --expect-final https://example.com/docs --expect-status 200 --max-hops 2

Final URL comparison is strict by default: both URLs are parsed and serialised, then compared exactly. --lax is only valid with --expect-final and applies exactly these fixed normalisations: strip trailing path slashes, treat HTTP and HTTPS as equivalent only with the same host and path, and remove utm_*, gclid, and fbclid query parameters. It does not tolerate www. changes, patterns, globs, or configurable rules.

Terminal and Markdown output report failed expectations as expected X, got Y. If masked query values are the only difference, they say so rather than implying the URLs are identical. When a chain does not terminate (for example, an asserted hop cap is exceeded or a redirect loop is found), terminal URL and status assertions are reported as failed but not evaluated: their actual value is null and their additive note explains why. A loop always fails a supplied --max-hops assertion, with actual: "loop". JSON adds an assertions array with kind, expected, actual, passed, and, where relevant, normalization or note. A lax pass that requires multiple combined normalizations has no single normalization value, so that field is absent.

What this will never assert

Assertions describe chain shape only: terminal URL, terminal status, and hop count. They will never assert headers, response bodies, or timing. Use an HTTP test framework such as hurl for those checks.

Batch mode

Trace a redirect map with --input <file> or --input - for stdin. Each non-blank, non-comment line contains a URL and optionally its expected final URL, separated by whitespace. Lines with three or more columns and invalid URLs are reported as failed rows while the rest of the input completes. Batch input is capped at 10,000 lines.

redirect-trace --input redirects.txt --concurrency 4 --expect-status 200 --format markdown > audit.md

Per-row expected finals use the strict comparator by default; --lax applies to every row. --expect-final and a positional URL cannot be combined with batch input. Batch requests use concurrency 2 by default, but requests from the same input hostname are always serialised. The batch exit status is the worst row result: clean 0, flagged 1, assertion failure 4, or transport/row parse failure 3; usage failures remain 2. Invalid input rows use the normal JSON trace shape where possible, including initialMethod, but have no truncated field because no trace was captured.

Batch terminal output streams one result line as each row completes, followed by an input-ordered summary table. A flagged row's detail is its first observed flag; failures and assertion mismatches take precedence over flags.

Secrets and determinism

Query values are masked by default when their case-insensitive key is one of:

token, access_token, refresh_token, code, secret, client_secret, key, api_key, password, pwd, sig, signature, auth, session, sid.

Pass --show-secrets only when raw values are safe to display or store.

Markdown and JSON are deterministic for the same captured trace: they omit timing by default and use stable rendering. Remote servers can still return different redirects between requests. --include-timing adds per-hop timing to Markdown and JSON and intentionally breaks byte-identical output; terminal output always shows live timing.

Flags

| Flag | Meaning | | --- | --- | | loop | A normalized URL repeats in the chain. | | loop-ignoring-params | A URL repeats after removing its query string; enabled with --ignore-params-loop. | | https-downgrade | A redirect moves from HTTPS to HTTP. | | cross-origin | Scheme, host, or port changes. | | host-change | Host changes between hops. | | port-change | Port changes between hops. | | method-semantic-transition | The selected initial method has notable redirect semantics for this status. | | terminal-error-status | The terminal response is 4xx or 5xx. |

Flags are observations for review, not assertions that a redirect is wrong.

Reference

| Flag | Default | Meaning | | --- | --- | --- | | <url> | required | One http or https URL to trace. | | --method <M> | GET | Method label for redirect-semantic analysis. Requests remain safe HEAD-then-GET with no body. | | --format <fmt> | terminal | terminal, markdown, or json. | | --max-hops <n> | 10 | When supplied, assert that the chain has at most <n> redirects; an exceeded assertion exits 4. Without it, the default engine limit is 10 redirects and an exceeded limit exits 3. | | --expect-final <url> | | Require an exact terminal HTTP(S) URL. | | --expect-status <code> | | Require the terminal integer HTTP status. | | --lax | off | Apply the three fixed opt-in normalisations to --expect-final. | | --timeout <ms> | 10000 | Per-request timeout in milliseconds. | | --show-secrets | off | Show raw sensitive query values. | | --ignore-params-loop | off | Also detect repeated URLs with query strings removed. | | --include-timing | off | Include timing in Markdown/JSON; breaks deterministic output. | | --input <file> | | Batch input file; use - for stdin. Cannot be combined with a positional URL or --expect-final. | | --concurrency <n> | 2 | Positive number of simultaneous batch traces across distinct input hosts. Batch input only. | | --help | | Print usage, examples, and exit codes. | | --version | | Print the CLI version. |

First release

Before merging the first release PR, configure npm trusted publishing for redirect-trace with this repository as its trusted publisher. This one-time npmjs setup is required before the workflow can publish with OIDC.

License

MIT - see LICENSE