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

htaccess-trace

v0.1.0

Published

Trace an Apache .htaccess rewrite from the command line: which RewriteRule fired, which RewriteCond failed, and what the URL became.

Readme

htaccess-trace

Your RewriteRule didn't fire and Apache won't tell you why.

htaccess-trace runs the rules on your machine and prints what happened to one URL, line by line: which rule matched, which RewriteCond failed and what it was actually tested against, what the path became after each pass, and where the request ended up.

$ npx htaccess-trace examples/wordpress.htaccess /blog/hello-world

Trace for /blog/hello-world

  line 5: RewriteRule ^index\.php$ — no match
  line 8: RewriteRule . — applied
    line 6: PASS  "/blog/hello-world" vs !-f
      assumption: cannot stat '/blog/hello-world' in simulation; assuming -f is false
    line 7: PASS  "/blog/hello-world" vs !-d
      assumption: cannot stat '/blog/hello-world' in simulation; assuming -d is false
    path: /blog/hello-world  ->  /index.php
  line 5: RewriteRule ^index\.php$ — applied
  Result: internally rewritten to /index.php after 2 passes. The browser URL does not change.

No server, no restart, no log to read backwards. Apache's own answer to this question is LogLevel rewrite:trace8 plus a restart plus a log file, which you do not have on shared hosting and do not want on production.

Run it

Node 16 or newer. Nothing else — no Apache, no PHP, no network access, nothing is uploaded.

$ npx htaccess-trace <file> <url>

or straight from the repository, without npm:

$ npx github:theseusbuilds/htaccess-trace <file> <url>

or clone and run it directly:

$ git clone https://github.com/theseusbuilds/htaccess-trace
$ cd htaccess-trace
$ ./bin/htaccess-trace.js examples/canonical-https-www.htaccess http://example.com/pricing

The URL argument takes what you would actually type: /blog/post, example.com/blog/post, or https://example.com/blog/post?ref=x. A bare word with no dot is treated as a path, not a hostname.

Lint a file in CI

--check skips the trace and reports only what is wrong with the file. It exits 1 if anything is at error severity, so it drops into a pipeline as-is.

$ htaccess-trace --check examples/broken.htaccess

The file

  warning line 5: Rewrite rules here are ignored by Apache: no RewriteEngine directive. Add "RewriteEngine On" above them.
  info    line 10: Flag "T=image/webp" is valid but not modelled by the simulator; the trace shows what Apache would do *ignoring* it.
  warning line 10: Unknown RewriteRule flag "NOCSAE". Did you mean "NOCASE"?
  warning line 13: R=404 is outside the 3xx redirect range; Apache treats non-3xx codes here as an error response, not a redirect.
  error   line 18: Invalid regular expression in RewriteRule pattern: Invalid regular expression: /^products/([0-9]+$/gu: Unterminated group

$ echo $?
1

This is not a syntax checker — the parser already reports malformed lines. These are the mistakes that parse fine and then behave wrong on the server.

What it simulates, and what it does not

Stated here rather than discovered after it gives you a confident wrong answer.

Simulated. RewriteRule and RewriteCond in a per-directory (.htaccess) context: pattern matching, backreferences, condition chaining with [OR], server variable expansion, the rewrite loop and the ten-pass limit Apache imposes on it, and the flags L, END, NC, QSA, QSD, R=, F and G.

Recognised, reported, not simulated. Every other flag. When your file uses one, the trace names it on the line rather than pretending it had no effect.

Not modelled. RewriteBase, RewriteMap, proxying ([P]), mod_alias (Redirect, RedirectMatch), server-config context, and the merge of parent-directory .htaccess files. These are parsed, so they do not break the file; they just do not participate.

Request headers are empty. A simulation has no incoming request, so %{HTTP_USER_AGENT}, %{HTTP_REFERER} and the rest expand to an empty string, and a condition on one is shown being tested against "". It is printed rather than silently skipped, because a rule that fires only for one browser should not look like a rule that never fires.

File tests are assumed false. RewriteCond %{REQUEST_FILENAME} !-f cannot be answered without your filesystem. The simulator assumes the file does not exist and prints that assumption on the line it affects. That is the right guess for a front controller and the wrong guess if you are debugging a rule that depends on a file being there.

Patterns are compiled by JavaScript, not by PCRE. They agree on ordinary rewrite patterns and disagree at the edges. Where a ruleset uses a construct JavaScript cannot compile — atomic groups, possessive quantifiers, \A \z \Z, POSIX character classes — the trace prints a warning above itself instead of guessing.

The same engine in three places

The parser, simulator and validator in src/core/ are the ones inside .htaccess Pro, a JetBrains IDE plugin, and behind the browser version of this tool. One implementation compiled three ways, not three programs that agree today and drift next month. The plugin, the web page and this CLI all compile that one directory, and a check refuses the release if any of them stops doing so.

| | where | cost | |---|---|---| | htaccess-trace | your terminal, your CI | free, MIT | | browser version | theseusbuilds.dev/htaccess-tester/ | free, no account | | .htaccess Pro | IntelliJ IDEA, PhpStorm, WebStorm and the rest | free during early access |

The plugin adds what a terminal cannot: the trace runs against the file open in your editor, double-clicking a line of the trace jumps to that line of the file, and the validator's findings are highlighted in the file itself as you work on it.

Examples

examples/ holds four real-world rule sets — WordPress, Laravel, a canonical HTTPS+www redirect, a legacy redirect table — and broken.htaccess, which is the file above.

$ ./bin/htaccess-trace.js examples/canonical-https-www.htaccess http://example.com/pricing
$ ./bin/htaccess-trace.js examples/laravel.htaccess /api/users/42
$ ./bin/htaccess-trace.js examples/legacy-redirects.htaccess /products/42
$ ./bin/htaccess-trace.js examples/legacy-redirects.htaccess /products/old-sku-1

The last two are the same rule set and the same rule. One rewrites and one does not, because ^products/([0-9]+)/?$ wants digits and old-sku-1 is not digits. That is the entire genre of bug this tool exists for, and the trace names the rule that declined rather than leaving you to stare at the file.

Tests

./test.sh runs every example through the built CLI and diffs the result against the recorded output in tests/expected/. It needs nothing but Node.

Source

src/core/ is the engine. src/cli/ is one file of argument parsing and printing around it. dist/ is that source compiled to JavaScript and committed, so the tool runs with no toolchain and npx works straight off a clone. Kotlin, compiled with Kotlin/JS.

Found a file it gets wrong?

That is the most useful thing you can send: open an issue with the rules and the URL, or email [email protected]. A file that traces wrong is a bug in the plugin too.

What gets fixed and what does not is written down in SUPPORT.md, so you know before you spend the time: wrong output and crashes are fixed, documented limits are limits.

Licence

MIT. See LICENSE.

Apache, mod_rewrite and JetBrains are trademarks of their respective owners. This project is not affiliated with or endorsed by the Apache Software Foundation or JetBrains s.r.o.