repomatic.pages_redirects module¶
A faithful Python replica of the engine Cloudflare Pages runs _redirects on.
Cloudflare’s documentation describes the file format; it does not describe the
accounting, and the accounting is where rules die. A site once lost the last 18
rules of its file for years this way, silently: wrangler pages deploy prints
nothing when the parser discards lines, and a dead redirect looks exactly like
a URL nobody visits. This module replicates the reference implementation so
lint-repo can audit a committed file the way production will read it, before
production reads it.
Transcribed on 2026-08-10 from the engine itself, not from the documentation:
Parsing:
packages/workers-shared/utils/configuration/parseRedirects.tsin cloudflare/workers-sdk, as bundled in wrangler 4.118 (the same code path Miniflare uses, and the same parser family the Pages asset server feeds on).Matching:
packages/workers-shared/asset-worker/src/utils/rules-engine.ts.
The three rules of the engine that the documentation does not state:
A static rule is only free while it appears before the first dynamic rule. The parser flips
canCreateStaticRuleto false permanently at the first source containing*or:placeholder. Every later rule, however static it looks, is charged against the dynamic budget.The dynamic budget is 100, and blowing it aborts the file. Rule 101 of that mixed stream does not get skipped: the parser breaks out of the loop, discarding every remaining line. Order is therefore not a style choice, it decides which rules exist.
Matching is anchored and literal about trailing slashes. A placeholder compiles to
[^/]+(at least one character, never a slash, never empty), a splat to.*(may be empty), and the whole source to^...$./a/:bdoes not match/x/y/and/a/*matches/a/with an empty splat.
- class repomatic.pages_redirects.Rule(source, destination, status, line_number)[source]¶
Bases:
object
- class repomatic.pages_redirects.Invalid(message, line=None, line_number=None)[source]¶
Bases:
object
- class repomatic.pages_redirects.ParseResult(rules=<factory>, invalid=<factory>, aborted_at_line=None)[source]¶
Bases:
object
- repomatic.pages_redirects.parse_redirects(text)[source]¶
The exact algorithm of
parseRedirects, budget accounting included.- Return type:
- repomatic.pages_redirects.misordered_statics(rules)[source]¶
Exact-source rules charged against the dynamic budget by their position.
The engine’s static budget (2000) only covers exact rules appearing before the first dynamic source; every exact rule after that point burns a slot of the dynamic budget (100) instead. Such a file still works while the budget holds, so this is the early warning: each rule returned here brings the file one line closer to the silent abort
parse_redirects()reports asaborted_at_line. The fix is always the same reorder, all exact rules first, all pattern rules second, which is behaviour-preserving because the asset server probes exact sources first regardless of file position.
- repomatic.pages_redirects.rule_pattern(source: str) Pattern[str][source]¶
Compile a rule source exactly the way
generateRuleRegExpdoes.Memoized:
apply_rule()recompiles the same dynamic rules once per probed path otherwise.
- repomatic.pages_redirects.apply_rule(rule, path)[source]¶
Return the destination for
path, or None if the rule does not match.
- repomatic.pages_redirects.sample_path(source)[source]¶
A concrete request path a rule source would have matched.
An exact source is already one, and is returned untouched, which is the case that matters: a dropped exact rule names the very URL that stops working. A pattern has no single answer, so each
:namestands in for itself and each*for one segment, yielding an illustration rather than a promise about live traffic.- Parameters:
source (
str) – Rule source, exact or patterned.- Return type:
- Returns:
A path that
rule_pattern()would match.
- repomatic.pages_redirects.discarded_rules(text, parsed)[source]¶
The rules the engine abandoned, recovered from the tail it never read.
parse_redirects()reports that it stopped and drops everything below, because that is what production does. Naming what was lost needs the tail parsed on its own, which is what this does, with the line numbers shifted back to where they sit in the real file.Caution
The tail is parsed with fresh budgets, so one long enough to exhaust them again reports only its first batch. Reading this as an illustration of what broke rather than an exhaustive inventory is the intent either way: the fix is the same reorder however many rules are below the line.
- Parameters:
text (
str) – The full_redirectssource.parsed (
ParseResult) – Whatparse_redirects()made of it.
- Return type:
- Returns:
The abandoned rules, empty when the parser read the whole file.
- repomatic.pages_redirects.evaluate(rules, path)[source]¶
First-match evaluation over the kept rules, the way the asset server runs it.
The server splits exact sources into a hash map probed first, then walks the dynamic rules in file order. The two passes below mirror that: an exact rule wins over a pattern that also matches, wherever each sits in the file, which is also what makes the statics-first reorder the lint recommends behaviour-preserving.