@escape-game-over/atlas 0.1.29 → 0.1.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/NOT-BUILT.md CHANGED
@@ -500,27 +500,22 @@ looking at" better than any link can.
500
500
 
501
501
  ### Should `/en-US/about-us` redirect when the default locale is unprefixed?
502
502
 
503
- **No. Only `/en-US` itself does.**
503
+ **Yes, by one wildcard — not by a rule per page.**
504
504
 
505
505
  With `prefixDefaultLocale: false` the default locale is served at `/about-us`
506
- and nothing is built under `/en-US/`. Claiming the prefixed root is one rule and
507
- answers the URL people actually type, having seen every other language wear a
508
- prefix. Claiming the pages under it is a different proposition: a rule per page,
509
- and slugs are translated per locale, so the URL a reader lands on by editing
510
- `/el-GR/sxetika-me-emas` down to English keeps the *Greek* slug —
506
+ and nothing is built under `/en-US/`. Claiming the pages under it one rule at a
507
+ time does not work: slugs are translated per locale, so the URL a reader lands
508
+ on by editing `/el-GR/sxetika-me-emas` down to English keeps the *Greek* slug —
511
509
  `/en-US/sxetika-me-emas`. Covering that means the cross product of the prefix
512
- with every locale's spelling of every slug, which is a `_redirects` of hundreds
513
- against the 2000 a host reads, all of it for URLs nothing has ever linked.
514
-
515
- This was built first, in the one-rule-per-default-locale-page form, which is
516
- worth naming because it looks complete and is not: it answers `/en-US/about-us`
517
- and misses `/en-US/sxetika-me-emas`, so the case that motivated it is the case
518
- it does not cover.
519
-
520
- A wildcard would sidestep the count — `/en-US/* /:splat 301` is one line — but
521
- not the translated slug, and it is Cloudflare's syntax rather than something
522
- `ResolvedRedirect` can carry to another host. The pages get the 404 page, which
523
- is what it is for.
510
+ with every locale's spelling of every slug, a `_redirects` of hundreds against
511
+ the 2000 a host reads.
512
+
513
+ `/en-US/* /:splat 301` is one line instead. It still does not cover the
514
+ translated slug — `/en-US/sxetika-me-emas` goes to `/sxetika-me-emas`, which
515
+ 404s — but that URL 404ed without the rule too, so it is never worse and it
516
+ answers every English slug. It is Cloudflare's syntax, which is what every site
517
+ deploys to. It is written last, so a stated `/en-US/old` rule still fires, and
518
+ left out if anything is built under the prefix.
524
519
 
525
520
  ---
526
521
 
package/docs/toolchain.md CHANGED
@@ -93,14 +93,16 @@ slug or a switched-off page moves the rule with the page:
93
93
  | -------------- | ------------- | ---------------------------------------------- |
94
94
  | `/` | `/en-US` | `prefixDefaultLocale: true` leaves `/` unowned |
95
95
  | `/en-US` | `/` | `prefixDefaultLocale: false` leaves it unbuilt |
96
+ | `/en-US/*` | `/:splat` | the same, for every page under the prefix |
96
97
  | `/news/page/1` | `/news` | page one is the bare slug, never `page/1` |
97
98
 
98
99
  The first two are the same rule in its two directions: whichever root the
99
100
  routing mode does not serve points at the one it does. Both together cost one
100
- rule; the third costs one per paginated list per language. Nothing here scales
101
- with the number of pages a site has — see [`NOT-BUILT.md`](NOT-BUILT.md) for why
102
- the prefixed form of every *page* is left to the 404 instead. A project that
103
- states its own rule for one of these paths keeps it: the inferred one steps
101
+ rule. The wildcard costs one more, and it is written last in the file, because
102
+ Cloudflare fires the first rule that matches and a stated `/en-US/old` rule has
103
+ to be reached before it. The page-one rule costs one per paginated list per
104
+ language. Nothing here scales with the number of pages a site has. A project
105
+ that states its own rule for one of these paths keeps it: the inferred one steps
104
106
  aside.
105
107
 
106
108
  The root points at whichever route has an empty slug, so nothing has to name a
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@escape-game-over/atlas",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "type": "module",
5
5
  "description": "Typed, data-driven machinery for static multi-locale, multi-deployment Astro sites.",
6
6
  "private": false,
package/src/site/api.ts CHANGED
@@ -309,14 +309,11 @@ export interface Site<
309
309
  * **What comes back is more than what went in.** lib adds the rules the
310
310
  * route table implies, for the URLs its own design leaves unpublished but
311
311
  * reachable: whichever site root the routing mode does not serve — `/` when
312
- * every locale is prefixed, `/en-US` when the default locale is not — and
313
- * `/news/page/1` for a list whose page one is the bare path. A rule you
314
- * state for one of those paths replaces the inferred one, so pass `[]` and
315
- * you still get a file worth writing.
316
- *
317
- * Pages under an unused locale prefix are *not* claimed: that is a rule per
318
- * page per language, and it still misses the reader who edits a translated
319
- * slug's prefix. See `NOT-BUILT.md`.
312
+ * every locale is prefixed, `/en-US` when the default locale is not — the
313
+ * pages under that unused `/en-US/`, as one `/en-US/* /:splat` wildcard
314
+ * written last, and `/news/page/1` for a list whose page one is the bare
315
+ * path. A rule you state for one of those paths replaces the inferred one,
316
+ * so pass `[]` and you still get a file worth writing.
320
317
  *
321
318
  * Returns the rules as data. Rendering is a separate step —
322
319
  * `buildCloudflareRedirects` writes the `_redirects` that Cloudflare and
@@ -513,10 +513,8 @@ export function createSite<
513
513
  * switched-off page moves the rule with it instead of leaving one pointing
514
514
  * at a 404.
515
515
  *
516
- * Two, and deliberately not a third per *page*: mirroring every URL a
517
- * project builds is a rule per page, and with slugs translated per locale
518
- * it is a rule per page per language — a file of hundreds against the 2000
519
- * a host reads, for URLs nothing ever linked. See `NOT-BUILT.md`.
516
+ * The pages under an unused prefix are `prefixSplat`'s, as one wildcard
517
+ * rather than a rule per page per language. See `NOT-BUILT.md`.
520
518
  *
521
519
  * Both permanent. Neither is a state that changes while the config stays as
522
520
  * it is: page one will never live at `/page/1`, and the root will not move
@@ -570,6 +568,32 @@ export function createSite<
570
568
  return [...root, ...pageOne];
571
569
  }
572
570
 
571
+ /**
572
+ * `/en-US/about` → `/about` when the default locale is unprefixed: the
573
+ * pages under the prefix `inferredRedirects` points at the root, in one
574
+ * Cloudflare wildcard. A translated slug under the wrong prefix still ends
575
+ * on the 404, one hop later — no worse than without it.
576
+ *
577
+ * Kept apart because it has to be written last: Cloudflare fires the first
578
+ * rule that matches, and a wildcard ahead of a stated `/en-US/old` rule
579
+ * would swallow it. Skipped when anything is built under the prefix, which
580
+ * the wildcard would make unreachable.
581
+ */
582
+ function prefixSplat(): readonly ResolvedRedirect[] {
583
+ if (prefixDefaultLocale) return [];
584
+ const prefix = `/${defaultLocale}/`;
585
+ for (const path of builtPaths) {
586
+ if (path.startsWith(prefix)) return [];
587
+ }
588
+ return [
589
+ {
590
+ from: `${prefix}*`,
591
+ to: "/:splat",
592
+ status: statusFor("permanent"),
593
+ },
594
+ ];
595
+ }
596
+
573
597
  function redirects(
574
598
  rules: readonly RedirectRule<RouteId, L>[]
575
599
  ): readonly ResolvedRedirect[] {
@@ -590,6 +614,7 @@ export function createSite<
590
614
  }));
591
615
 
592
616
  const claimed = new Set(stated.map((rule) => rule.from));
617
+ const splat = prefixSplat().filter((rule) => !claimed.has(rule.from));
593
618
  const inferred = inferredRedirects().filter(
594
619
  // Dropped rather than reported, both times, because neither is a
595
620
  // mistake anyone made: a rule the project wrote for one of these
@@ -604,7 +629,7 @@ export function createSite<
604
629
  // So a rule that shadows a real page is rejected rather than
605
630
  // sitting dead in the file.
606
631
  builtPaths,
607
- rules: [...inferred, ...stated],
632
+ rules: [...inferred, ...stated, ...splat],
608
633
  });
609
634
  }
610
635