@escape-game-over/atlas 0.1.28 → 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 +13 -18
- package/docs/toolchain.md +6 -4
- package/package.json +2 -2
- package/src/redirects.ts +3 -2
- package/src/site/api.ts +5 -8
- package/src/site/create.ts +35 -8
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
|
-
**
|
|
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
|
|
507
|
-
|
|
508
|
-
|
|
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,
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
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
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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.
|
|
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,
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"@biomejs/biome": "2.5.14",
|
|
55
55
|
"@types/node": "26.6.2",
|
|
56
56
|
"@vitest/coverage-istanbul": "5.0.1",
|
|
57
|
-
"astro": "7.3.
|
|
57
|
+
"astro": "7.3.4",
|
|
58
58
|
"typescript": "6.0.3",
|
|
59
59
|
"vitest": "5.0.1"
|
|
60
60
|
}
|
package/src/redirects.ts
CHANGED
|
@@ -101,11 +101,12 @@ export type ValidateRedirectTargets<Rules> = {
|
|
|
101
101
|
* against what this project builds and its URL is derived — a redirect cannot
|
|
102
102
|
* outlive the page it points at, or miss a slug that was retranslated. The
|
|
103
103
|
* locale is optional and defaults to the site's own, since an old URL usually
|
|
104
|
-
* predates translation.
|
|
104
|
+
* predates translation. `page` names a page of a paginated list, and is refused
|
|
105
|
+
* the same way `pathFor` refuses it when the list has no such page.
|
|
105
106
|
*/
|
|
106
107
|
export type RedirectTarget<Id extends string, L extends string> =
|
|
107
108
|
| ExternalUrl
|
|
108
|
-
| { readonly route: Id; readonly locale?: L }
|
|
109
|
+
| { readonly route: Id; readonly locale?: L; readonly page?: number }
|
|
109
110
|
/**
|
|
110
111
|
* A file served verbatim from `public/` — a PDF, a spreadsheet.
|
|
111
112
|
*
|
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 —
|
|
313
|
-
*
|
|
314
|
-
*
|
|
315
|
-
* you
|
|
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
|
package/src/site/create.ts
CHANGED
|
@@ -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
|
-
*
|
|
517
|
-
*
|
|
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[] {
|
|
@@ -577,17 +601,20 @@ export function createSite<
|
|
|
577
601
|
from: rule.from,
|
|
578
602
|
// Three kinds of target, told apart by shape: a string is external
|
|
579
603
|
// and passes through, `file` is served verbatim from `public/`, and
|
|
580
|
-
// a route is resolved to whatever URL it has in the locale
|
|
581
|
-
// for.
|
|
604
|
+
// a route is resolved to whatever URL it has in the locale and on
|
|
605
|
+
// the page asked for.
|
|
582
606
|
to: ((): string => {
|
|
583
607
|
if (typeof rule.to === "string") return rule.to;
|
|
584
608
|
if ("file" in rule.to) return rule.to.file;
|
|
585
|
-
return pathFor(rule.to.route, rule.to.locale ?? defaultLocale
|
|
609
|
+
return pathFor(rule.to.route, rule.to.locale ?? defaultLocale, {
|
|
610
|
+
page: rule.to.page,
|
|
611
|
+
});
|
|
586
612
|
})(),
|
|
587
613
|
status: statusFor(rule.kind),
|
|
588
614
|
}));
|
|
589
615
|
|
|
590
616
|
const claimed = new Set(stated.map((rule) => rule.from));
|
|
617
|
+
const splat = prefixSplat().filter((rule) => !claimed.has(rule.from));
|
|
591
618
|
const inferred = inferredRedirects().filter(
|
|
592
619
|
// Dropped rather than reported, both times, because neither is a
|
|
593
620
|
// mistake anyone made: a rule the project wrote for one of these
|
|
@@ -602,7 +629,7 @@ export function createSite<
|
|
|
602
629
|
// So a rule that shadows a real page is rejected rather than
|
|
603
630
|
// sitting dead in the file.
|
|
604
631
|
builtPaths,
|
|
605
|
-
rules: [...inferred, ...stated],
|
|
632
|
+
rules: [...inferred, ...stated, ...splat],
|
|
606
633
|
});
|
|
607
634
|
}
|
|
608
635
|
|