@escape-game-over/atlas 0.1.1 → 0.1.3
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 +48 -7
- package/docs/NOT-BUILT.md +84 -0
- package/docs/toolchain.md +49 -22
- package/package.json +11 -6
- package/src/astro/MetaTags.astro +2 -4
- package/src/astro/build-cache.ts +80 -0
- package/src/astro/carousel.ts +342 -0
- package/src/astro/dev-log.ts +128 -0
- package/src/astro/dom.ts +53 -0
- package/src/astro/element.ts +259 -0
- package/src/astro/filters.ts +457 -0
- package/src/astro/images.ts +73 -0
- package/src/astro/site-routes.ts +126 -21
- package/src/astro/tsconfig.json +8 -0
- package/src/contact-form.ts +177 -0
- package/src/index.ts +7 -0
- package/src/meta/index.ts +79 -32
- package/src/routes/define.ts +3 -1
- package/src/routes/resolve.ts +28 -3
- package/src/site/api.ts +12 -0
- package/src/site/create.ts +144 -43
package/src/site/create.ts
CHANGED
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
listRouteEntries,
|
|
35
35
|
mergeRoutes,
|
|
36
36
|
type PathContext,
|
|
37
|
+
pageOneAlias,
|
|
37
38
|
pagePath,
|
|
38
39
|
type RouteEntry,
|
|
39
40
|
slugFor,
|
|
@@ -218,8 +219,31 @@ export function createSite<
|
|
|
218
219
|
});
|
|
219
220
|
}
|
|
220
221
|
|
|
222
|
+
/**
|
|
223
|
+
* The sitemap, built once and handed to everyone who asks.
|
|
224
|
+
*
|
|
225
|
+
* Three callers, and two of them want one string out of it: `robots()` and
|
|
226
|
+
* `llms()` each advertise `entry.url`, so an unmemoised call derived every
|
|
227
|
+
* URL on the site — each with its alternates, each joined to the origin —
|
|
228
|
+
* three times over to answer a question about a filename. `siteRoutes`
|
|
229
|
+
* then does the whole thing again on every dev-server request that hits a
|
|
230
|
+
* generated file.
|
|
231
|
+
*
|
|
232
|
+
* Safe to hold because everything it reads is settled before this closure
|
|
233
|
+
* exists: `entries`, `localeMeta` and the origin are fixed for the life of
|
|
234
|
+
* a `Site`, and `alternatesFor` reads nothing else. A dev server picks up
|
|
235
|
+
* an edited route by importing the module again and getting a *new* site,
|
|
236
|
+
* not by this one answering differently — so there is nothing here for a
|
|
237
|
+
* cache to go stale against.
|
|
238
|
+
*
|
|
239
|
+
* Sharing the `Sitemap` itself is safe for the same reason, with one part
|
|
240
|
+
* that would not be: `staticPaths` maps a fresh array per call, which is
|
|
241
|
+
* what an SSG router demands of it.
|
|
242
|
+
*/
|
|
243
|
+
let built: Sitemap | undefined;
|
|
221
244
|
function sitemap(): Sitemap {
|
|
222
|
-
return
|
|
245
|
+
if (built !== undefined) return built;
|
|
246
|
+
built = buildSitemap<L, RouteId>({
|
|
223
247
|
siteUrl,
|
|
224
248
|
name: config_.sitemap?.name ?? "sitemap.xml",
|
|
225
249
|
entryLimit: config_.sitemap?.entryLimit,
|
|
@@ -227,6 +251,7 @@ export function createSite<
|
|
|
227
251
|
localeMeta,
|
|
228
252
|
alternatesFor,
|
|
229
253
|
});
|
|
254
|
+
return built;
|
|
230
255
|
}
|
|
231
256
|
|
|
232
257
|
// Registry declaration order, filtered to what this project builds.
|
|
@@ -242,12 +267,24 @@ export function createSite<
|
|
|
242
267
|
pathContext
|
|
243
268
|
) as readonly RouteEntry<L, RouteId>[];
|
|
244
269
|
|
|
245
|
-
//
|
|
246
|
-
//
|
|
270
|
+
// The page the site's root resolves to, wherever routing puts it: `/` when
|
|
271
|
+
// the default locale is unprefixed, `/en-US` when it is not. Undefined
|
|
272
|
+
// unless a route actually claims that path — nothing has to own the root.
|
|
247
273
|
const localeRootPath = buildPath("", defaultLocale, pathContext);
|
|
248
|
-
const rootEntry =
|
|
249
|
-
|
|
250
|
-
|
|
274
|
+
const rootEntry = entries.find(
|
|
275
|
+
(entry) =>
|
|
276
|
+
entry.locale === defaultLocale && entry.path === localeRootPath
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Every path this build serves.
|
|
281
|
+
*
|
|
282
|
+
* Read by `redirects()` twice over: to reject a stated rule that shadows a
|
|
283
|
+
* real page, and to keep an inferred one from doing the same.
|
|
284
|
+
*/
|
|
285
|
+
const builtPaths: ReadonlySet<string> = new Set(
|
|
286
|
+
entries.map((entry) => entry.path)
|
|
287
|
+
);
|
|
251
288
|
|
|
252
289
|
function staticPaths(param: string): StaticPath<L, RouteId>[] {
|
|
253
290
|
const paths: StaticPath<L, RouteId>[] = entries.map((entry) => ({
|
|
@@ -285,7 +322,18 @@ export function createSite<
|
|
|
285
322
|
// Umami only, tagged so the misses read on their own — the point
|
|
286
323
|
// of measuring this page is finding a redirect somebody forgot,
|
|
287
324
|
// not counting the people who mistype. See `notFoundAnalytics`.
|
|
288
|
-
|
|
325
|
+
//
|
|
326
|
+
// `checkedIcon()` rather than `project.icon`: the 404 is held to the
|
|
327
|
+
// same square-and-PNG rules as every other page. It is also the one
|
|
328
|
+
// page that could plausibly render before any other, so letting it
|
|
329
|
+
// read the icon unchecked would move where a bad one is caught.
|
|
330
|
+
tags: buildNotFoundMeta({
|
|
331
|
+
title,
|
|
332
|
+
analytics: project.analytics,
|
|
333
|
+
icon: checkedIcon(),
|
|
334
|
+
themeColor: project.themeColor,
|
|
335
|
+
colorScheme: project.colorScheme,
|
|
336
|
+
}),
|
|
289
337
|
// Nothing. The body channel exists for Tag Manager's `<noscript>`,
|
|
290
338
|
// and no Google tag reaches this page.
|
|
291
339
|
bodyTags: [],
|
|
@@ -450,55 +498,108 @@ export function createSite<
|
|
|
450
498
|
return joinUrl(siteUrl, path);
|
|
451
499
|
}
|
|
452
500
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
501
|
+
/**
|
|
502
|
+
* The rules lib writes for itself, from the route table rather than from
|
|
503
|
+
* anything a project states.
|
|
504
|
+
*
|
|
505
|
+
* Both answer one question: which URL that this build publishes nothing at
|
|
506
|
+
* will be asked for anyway? Each is a path the design deliberately does not
|
|
507
|
+
* serve — the root the routing mode leaves unowned, and the page one that
|
|
508
|
+
* is not numbered — and a path nobody serves on purpose is still a path
|
|
509
|
+
* somebody types. Both are read off `entries`, so a retranslated slug or a
|
|
510
|
+
* switched-off page moves the rule with it instead of leaving one pointing
|
|
511
|
+
* at a 404.
|
|
512
|
+
*
|
|
513
|
+
* Two, and deliberately not a third per *page*: mirroring every URL a
|
|
514
|
+
* project builds is a rule per page, and with slugs translated per locale
|
|
515
|
+
* it is a rule per page per language — a file of hundreds against the 2000
|
|
516
|
+
* a host reads, for URLs nothing ever linked. See `NOT-BUILT.md`.
|
|
517
|
+
*
|
|
518
|
+
* Both permanent. Neither is a state that changes while the config stays as
|
|
519
|
+
* it is: page one will never live at `/page/1`, and the root will not move
|
|
520
|
+
* while `prefixDefaultLocale` is what it is.
|
|
521
|
+
*/
|
|
522
|
+
function inferredRedirects(): readonly ResolvedRedirect[] {
|
|
523
|
+
const status = statusFor("permanent");
|
|
524
|
+
|
|
525
|
+
// One root is served and the other is not, and which is which is what
|
|
526
|
+
// `prefixDefaultLocale` decides: prefixed, the site answers on `/en-US`
|
|
527
|
+
// and nothing owns `/`; unprefixed, it answers on `/` and `/en-US` is a
|
|
528
|
+
// URL nobody built — the one people still type, having seen every other
|
|
529
|
+
// language wear its prefix. Whichever of the two this build does not
|
|
530
|
+
// serve is pointed at the one it does.
|
|
458
531
|
//
|
|
459
532
|
// A redirect rather than a page: a meta-refresh stub at `/` is a soft
|
|
460
533
|
// redirect, which search engines follow slowly and weigh less, and it
|
|
461
534
|
// costs a render before the reader goes anywhere. The two cannot both
|
|
462
535
|
// exist, since a static host serves the file and the rule never fires —
|
|
463
|
-
// which is what `builtPaths`
|
|
464
|
-
|
|
465
|
-
// the route table and follows a retranslated slug.
|
|
466
|
-
const root: readonly RedirectRule<RouteId, L>[] =
|
|
536
|
+
// which is what `builtPaths` rejects.
|
|
537
|
+
const root: readonly ResolvedRedirect[] =
|
|
467
538
|
rootEntry === undefined
|
|
468
539
|
? []
|
|
469
540
|
: [
|
|
470
541
|
{
|
|
471
|
-
from: "/"
|
|
472
|
-
to:
|
|
473
|
-
|
|
474
|
-
locale: rootEntry.locale,
|
|
475
|
-
},
|
|
476
|
-
// The root will never own a page again while every
|
|
477
|
-
// locale is prefixed, which is what permanent means.
|
|
478
|
-
kind: "permanent",
|
|
542
|
+
from: prefixDefaultLocale ? "/" : `/${defaultLocale}`,
|
|
543
|
+
to: rootEntry.path,
|
|
544
|
+
status,
|
|
479
545
|
},
|
|
480
546
|
];
|
|
481
547
|
|
|
548
|
+
// `/news/page/1` for every list that runs past one page, in every
|
|
549
|
+
// language it is published in — see `pageOneAlias` for who asks for it.
|
|
550
|
+
//
|
|
551
|
+
// Only a route that *has* a second page: `1` or omitted is an ordinary
|
|
552
|
+
// route, and `/about/page/1` is a URL nobody has had a reason to type.
|
|
553
|
+
// The alias for a list that once ran longer and no longer does is the
|
|
554
|
+
// one this misses, and it is not worth a rule per page of the site.
|
|
555
|
+
const pageOne = entries
|
|
556
|
+
.filter(
|
|
557
|
+
(entry) =>
|
|
558
|
+
entry.page === 1 &&
|
|
559
|
+
(resolved[entry.routeId]?.pages ?? 1) > 1
|
|
560
|
+
)
|
|
561
|
+
.map((entry) => ({
|
|
562
|
+
from: pageOneAlias(entry.path, entry.locale, pathContext),
|
|
563
|
+
to: entry.path,
|
|
564
|
+
status,
|
|
565
|
+
}));
|
|
566
|
+
|
|
567
|
+
return [...root, ...pageOne];
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function redirects(
|
|
571
|
+
rules: readonly RedirectRule<RouteId, L>[]
|
|
572
|
+
): readonly ResolvedRedirect[] {
|
|
573
|
+
const stated: readonly ResolvedRedirect[] = rules.map((rule) => ({
|
|
574
|
+
from: rule.from,
|
|
575
|
+
// Three kinds of target, told apart by shape: a string is external
|
|
576
|
+
// and passes through, `file` is served verbatim from `public/`, and
|
|
577
|
+
// a route is resolved to whatever URL it has in the locale asked
|
|
578
|
+
// for.
|
|
579
|
+
to: ((): string => {
|
|
580
|
+
if (typeof rule.to === "string") return rule.to;
|
|
581
|
+
if ("file" in rule.to) return rule.to.file;
|
|
582
|
+
return pathFor(rule.to.route, rule.to.locale ?? defaultLocale);
|
|
583
|
+
})(),
|
|
584
|
+
status: statusFor(rule.kind),
|
|
585
|
+
}));
|
|
586
|
+
|
|
587
|
+
const claimed = new Set(stated.map((rule) => rule.from));
|
|
588
|
+
const inferred = inferredRedirects().filter(
|
|
589
|
+
// Dropped rather than reported, both times, because neither is a
|
|
590
|
+
// mistake anyone made: a rule the project wrote for one of these
|
|
591
|
+
// paths is a decision and this is a default, and a page built at
|
|
592
|
+
// one of them is a page, which outranks a mirror of another page.
|
|
593
|
+
// The same clash among *stated* rules still throws below — there,
|
|
594
|
+
// both sides were written on purpose and only one can fire.
|
|
595
|
+
(rule) => !claimed.has(rule.from) && !builtPaths.has(rule.from)
|
|
596
|
+
);
|
|
597
|
+
|
|
482
598
|
return buildRedirects({
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
builtPaths
|
|
486
|
-
rules: [...
|
|
487
|
-
from: rule.from,
|
|
488
|
-
// Three kinds of target, told apart by shape: a string is
|
|
489
|
-
// external and passes through, `file` is served verbatim from
|
|
490
|
-
// `public/`, and a route is resolved to whatever URL it has in
|
|
491
|
-
// the locale asked for.
|
|
492
|
-
to: ((): string => {
|
|
493
|
-
if (typeof rule.to === "string") return rule.to;
|
|
494
|
-
if ("file" in rule.to) return rule.to.file;
|
|
495
|
-
return pathFor(
|
|
496
|
-
rule.to.route,
|
|
497
|
-
rule.to.locale ?? defaultLocale
|
|
498
|
-
);
|
|
499
|
-
})(),
|
|
500
|
-
status: statusFor(rule.kind),
|
|
501
|
-
})),
|
|
599
|
+
// So a rule that shadows a real page is rejected rather than
|
|
600
|
+
// sitting dead in the file.
|
|
601
|
+
builtPaths,
|
|
602
|
+
rules: [...inferred, ...stated],
|
|
502
603
|
});
|
|
503
604
|
}
|
|
504
605
|
|