@escape-game-over/atlas 0.1.70 → 0.1.71
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/README.md +1 -0
- package/package.json +1 -1
- package/src/redirects.ts +45 -7
- package/src/site/create.ts +14 -0
package/README.md
CHANGED
|
@@ -119,6 +119,7 @@ it returns and nothing else, resolves it, and writes `_redirects`:
|
|
|
119
119
|
// projects/rome/redirects.ts
|
|
120
120
|
export default redirects([
|
|
121
121
|
{ from: "/jobs", to: { route: "careers" }, kind: "permanent" }, // ✗ if Rome has no careers page
|
|
122
|
+
{ from: "/en/*", to: { route: "home", locale: "en-US", splat: true }, kind: "permanent" }, // /en/faq → /en-US/faq
|
|
122
123
|
])
|
|
123
124
|
```
|
|
124
125
|
|
package/package.json
CHANGED
package/src/redirects.ts
CHANGED
|
@@ -71,13 +71,19 @@ export type SitePath = UrlPath;
|
|
|
71
71
|
*/
|
|
72
72
|
export type ExternalUrl = HttpsUrl;
|
|
73
73
|
|
|
74
|
+
/** A `splat` target whose source has no `*` to carry: there is no rest to keep. */
|
|
75
|
+
export interface SplatNeedsWildcard<From extends string> {
|
|
76
|
+
readonly __SPLAT_WITHOUT_WILDCARD__: `"${From}" has no "/*" to carry over: a rule with \`splat: true\` needs a source ending in "/*"`;
|
|
77
|
+
}
|
|
78
|
+
|
|
74
79
|
/**
|
|
75
|
-
* Rejects an external target that is not `https://`,
|
|
80
|
+
* Rejects an external target that is not `https://`, and a `splat` target
|
|
81
|
+
* whose source has no wildcard, naming each.
|
|
76
82
|
*
|
|
77
|
-
* Applied to the rules array in parameter position, so a bad
|
|
78
|
-
* own line rather than as the whole array failing to match. The check
|
|
79
|
-
* `ValidHttpsUrl`, shared with the site origin — one idea of an
|
|
80
|
-
* stated once.
|
|
83
|
+
* Applied to the rules array in parameter position, so a bad rule reports on
|
|
84
|
+
* its own line rather than as the whole array failing to match. The URL check
|
|
85
|
+
* itself is `ValidHttpsUrl`, shared with the site origin — one idea of an
|
|
86
|
+
* acceptable URL, stated once.
|
|
81
87
|
*/
|
|
82
88
|
export type ValidateRedirectTargets<Rules> = {
|
|
83
89
|
readonly [K in keyof Rules]: Rules[K] extends {
|
|
@@ -90,7 +96,18 @@ export type ValidateRedirectTargets<Rules> = {
|
|
|
90
96
|
? ValidHttpsUrl<To>
|
|
91
97
|
: Rules[K][P];
|
|
92
98
|
}
|
|
93
|
-
: Rules[K]
|
|
99
|
+
: Rules[K] extends {
|
|
100
|
+
readonly from: infer From extends string;
|
|
101
|
+
readonly to: { readonly splat: true };
|
|
102
|
+
}
|
|
103
|
+
? From extends `${string}/*`
|
|
104
|
+
? Rules[K]
|
|
105
|
+
: {
|
|
106
|
+
readonly [P in keyof Rules[K]]: P extends "from"
|
|
107
|
+
? SplatNeedsWildcard<From>
|
|
108
|
+
: Rules[K][P];
|
|
109
|
+
}
|
|
110
|
+
: Rules[K];
|
|
94
111
|
};
|
|
95
112
|
|
|
96
113
|
/**
|
|
@@ -105,7 +122,28 @@ export type ValidateRedirectTargets<Rules> = {
|
|
|
105
122
|
*/
|
|
106
123
|
export type RedirectTarget<Id extends string, L extends string> =
|
|
107
124
|
| ExternalUrl
|
|
108
|
-
| {
|
|
125
|
+
| {
|
|
126
|
+
readonly route: Id;
|
|
127
|
+
readonly locale?: L;
|
|
128
|
+
readonly page?: number;
|
|
129
|
+
readonly splat?: never;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* A page, with whatever the source's `*` matched kept after its path:
|
|
133
|
+
* `/en/*` to `{ route: "home", locale: "en-US", splat: true }` sends
|
|
134
|
+
* `/en/faq` to `/en-US/faq`.
|
|
135
|
+
*
|
|
136
|
+
* For an old prefix whose pages kept their slugs, so one rule answers every
|
|
137
|
+
* URL under it. The target stays a route, so the prefix it lands on is
|
|
138
|
+
* derived like every other link rather than spelled out by hand. Not with
|
|
139
|
+
* `page`: the rest of a path cannot follow a page number.
|
|
140
|
+
*/
|
|
141
|
+
| {
|
|
142
|
+
readonly route: Id;
|
|
143
|
+
readonly locale?: L;
|
|
144
|
+
readonly splat: true;
|
|
145
|
+
readonly page?: never;
|
|
146
|
+
}
|
|
109
147
|
/**
|
|
110
148
|
* A file served verbatim from `public/` — a PDF, a spreadsheet.
|
|
111
149
|
*
|
package/src/site/create.ts
CHANGED
|
@@ -610,6 +610,20 @@ export function createSite<
|
|
|
610
610
|
to: ((): string => {
|
|
611
611
|
if (typeof rule.to === "string") return rule.to;
|
|
612
612
|
if ("file" in rule.to) return rule.to.file;
|
|
613
|
+
if (rule.to.splat === true) {
|
|
614
|
+
// Checked here too, for a rule the types could not see:
|
|
615
|
+
// without a `*`, the host has nothing to put in `:splat`.
|
|
616
|
+
if (!rule.from.endsWith("/*")) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
`Redirect "${rule.from}" keeps the rest of the path (\`splat: true\`) but has no "/*" to take it from.`
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
const path = pathFor(
|
|
622
|
+
rule.to.route,
|
|
623
|
+
rule.to.locale ?? defaultLocale
|
|
624
|
+
);
|
|
625
|
+
return path === "/" ? "/:splat" : `${path}/:splat`;
|
|
626
|
+
}
|
|
613
627
|
return pathFor(rule.to.route, rule.to.locale ?? defaultLocale, {
|
|
614
628
|
page: rule.to.page,
|
|
615
629
|
});
|