@escape-game-over/atlas 0.1.33 → 0.1.35
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/package.json +2 -2
- package/src/analytics/google.ts +2 -1
- package/src/analytics/umami.ts +2 -1
- package/src/hours.ts +3 -3
- package/src/index.ts +3 -2
- package/src/jsonld/business.ts +2 -1
- package/src/jsonld/quantity.ts +2 -1
- package/src/meta/index.ts +2 -1
- package/src/meta/share-image.ts +31 -6
- package/src/money.ts +7 -11
- package/src/types.ts +3 -0
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.35",
|
|
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.5",
|
|
58
58
|
"typescript": "6.0.3",
|
|
59
59
|
"vitest": "5.0.1"
|
|
60
60
|
}
|
package/src/analytics/google.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { NonEmpty } from "../types.ts";
|
|
1
2
|
import { warn } from "../warn.ts";
|
|
2
3
|
import { type AnalyticsTags, literal, preconnect } from "./tags.ts";
|
|
3
4
|
|
|
@@ -129,7 +130,7 @@ export interface GoogleSettings {
|
|
|
129
130
|
* banner that loads slowly. See `DENIED_BY_DEFAULT` for exactly what the
|
|
130
131
|
* unstated case emits.
|
|
131
132
|
*/
|
|
132
|
-
readonly consent?:
|
|
133
|
+
readonly consent?: NonEmpty<ConsentDefaults>;
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
/**
|
package/src/analytics/umami.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { MetaTag } from "../meta/tag.ts";
|
|
2
|
+
import type { NonEmpty } from "../types.ts";
|
|
2
3
|
import { type HttpsUrl, joinUrl, type UrlPath } from "../url.ts";
|
|
3
4
|
import { warn } from "../warn.ts";
|
|
4
5
|
import { preconnect } from "./tags.ts";
|
|
@@ -142,7 +143,7 @@ export interface UmamiSettings {
|
|
|
142
143
|
* `www.` traffic of every site serving both. Stating it is a moment's
|
|
143
144
|
* thought; the alternative is a number that reads low forever.
|
|
144
145
|
*/
|
|
145
|
-
readonly domains:
|
|
146
|
+
readonly domains: NonEmpty<string>;
|
|
146
147
|
/**
|
|
147
148
|
* Where the collected data is *sent*, if not where the script came from.
|
|
148
149
|
*
|
package/src/hours.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Digit } from "./types.ts";
|
|
1
|
+
import type { Digit, NonEmpty } from "./types.ts";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* When a venue is open, as a week rather than a sentence.
|
|
@@ -69,7 +69,7 @@ export interface TimeRange {
|
|
|
69
69
|
*
|
|
70
70
|
* Several spans cover a venue that shuts for the afternoon.
|
|
71
71
|
*/
|
|
72
|
-
export type DayHours = "closed" |
|
|
72
|
+
export type DayHours = "closed" | NonEmpty<TimeRange>;
|
|
73
73
|
|
|
74
74
|
/**
|
|
75
75
|
* The whole week, every day stated.
|
|
@@ -83,7 +83,7 @@ export type WeeklyHours = Readonly<Record<Weekday, DayHours>>;
|
|
|
83
83
|
/** Days that share the same hours, ready to be printed or serialised. */
|
|
84
84
|
export interface HoursGroup {
|
|
85
85
|
/** Every day in the group, in week order. */
|
|
86
|
-
readonly days:
|
|
86
|
+
readonly days: NonEmpty<Weekday>;
|
|
87
87
|
/**
|
|
88
88
|
* The same days as consecutive spans: `[["monday", "thursday"],
|
|
89
89
|
* ["sunday", "sunday"]]` is what a footer renders as "Mon–Thu & Sun".
|
package/src/index.ts
CHANGED
|
@@ -160,7 +160,8 @@ export {
|
|
|
160
160
|
type ArticleContent,
|
|
161
161
|
buildMeta,
|
|
162
162
|
buildNotFoundMeta,
|
|
163
|
-
|
|
163
|
+
fitDescription,
|
|
164
|
+
fitTitle,
|
|
164
165
|
type MetaContent,
|
|
165
166
|
type MetaTag,
|
|
166
167
|
type OpenGraphType,
|
|
@@ -233,7 +234,7 @@ export type {
|
|
|
233
234
|
StaticPath,
|
|
234
235
|
} from "./site/index.ts";
|
|
235
236
|
export type { Sitemap, SitemapConfig } from "./sitemap.ts";
|
|
236
|
-
export type { IsoDate, Letter, StringKeys } from "./types.ts";
|
|
237
|
+
export type { IsoDate, Letter, NonEmpty, StringKeys } from "./types.ts";
|
|
237
238
|
export {
|
|
238
239
|
absoluteUrl,
|
|
239
240
|
type Hash,
|
package/src/jsonld/business.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
} from "../contact.ts";
|
|
8
8
|
import type { WeeklyHours } from "../hours.ts";
|
|
9
9
|
import type { ImageAsset } from "../image.ts";
|
|
10
|
+
import type { NonEmpty } from "../types.ts";
|
|
10
11
|
import { absoluteUrl, type HttpsUrl } from "../url.ts";
|
|
11
12
|
import { businessId, type OrganizationId } from "./ids.ts";
|
|
12
13
|
import { alternateName, type JsonLdNode, schemaType } from "./node.ts";
|
|
@@ -126,7 +127,7 @@ export interface LocalBusinessInput {
|
|
|
126
127
|
* Reference:
|
|
127
128
|
* <https://developers.google.com/search/docs/appearance/structured-data/local-business>
|
|
128
129
|
*/
|
|
129
|
-
readonly type?: string |
|
|
130
|
+
readonly type?: string | NonEmpty<string>;
|
|
130
131
|
/**
|
|
131
132
|
* Pages elsewhere that are unmistakably this same venue.
|
|
132
133
|
*
|
package/src/jsonld/quantity.ts
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
quantitiesOf,
|
|
6
6
|
quantityRange,
|
|
7
7
|
} from "../money.ts";
|
|
8
|
+
import type { NonEmpty } from "../types.ts";
|
|
8
9
|
import type { JsonLdNode } from "./node.ts";
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -46,7 +47,7 @@ export function quantitativeValue(quantity: Quantity): JsonLdNode {
|
|
|
46
47
|
* Reference: <https://schema.org/QuantitativeValue>
|
|
47
48
|
*/
|
|
48
49
|
export function quantitiesFor(
|
|
49
|
-
tiers:
|
|
50
|
+
tiers: NonEmpty<PriceTier>
|
|
50
51
|
): JsonLdNode | readonly JsonLdNode[] {
|
|
51
52
|
return isContiguous(tiers)
|
|
52
53
|
? quantitativeValue(quantityRange(tiers))
|
package/src/meta/index.ts
CHANGED
package/src/meta/share-image.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ImageAsset } from "../image.ts";
|
|
2
|
+
import type { NonEmpty } from "../types.ts";
|
|
2
3
|
import { absoluteUrl, type HttpsUrl } from "../url.ts";
|
|
3
4
|
import { warn } from "../warn.ts";
|
|
4
5
|
import { link, type MetaTag } from "./tag.ts";
|
|
@@ -109,11 +110,8 @@ const NO_PREVIEW_BELOW = 200;
|
|
|
109
110
|
* the tightest is the one that counts. Facebook's mobile preview is that: a line
|
|
110
111
|
* under it survives everywhere, while a line written to Google's 160 is already
|
|
111
112
|
* clipped in a share. A title is cut far sooner, by the surface people search on.
|
|
112
|
-
*
|
|
113
|
-
* Exported so a site that picks between copy variants fits them to these
|
|
114
|
-
* numbers rather than a copy of them.
|
|
115
113
|
*/
|
|
116
|
-
|
|
114
|
+
const LENGTH_LIMITS = {
|
|
117
115
|
title: {
|
|
118
116
|
limit: 60,
|
|
119
117
|
why: "a search result cuts a title around there, and the end of it is usually the part that identifies the page",
|
|
@@ -131,16 +129,43 @@ export const META_LENGTH_LIMITS = {
|
|
|
131
129
|
* nobody notices without being told.
|
|
132
130
|
*/
|
|
133
131
|
export function warnIfClipped(
|
|
134
|
-
field: keyof typeof
|
|
132
|
+
field: keyof typeof LENGTH_LIMITS,
|
|
135
133
|
value: string,
|
|
136
134
|
at: string
|
|
137
135
|
): void {
|
|
138
|
-
const { limit, why } =
|
|
136
|
+
const { limit, why } = LENGTH_LIMITS[field];
|
|
139
137
|
if (value.length <= limit) return;
|
|
140
138
|
|
|
141
139
|
warn(at, `${field} is ${value.length} characters, past ~${limit}: ${why}.`);
|
|
142
140
|
}
|
|
143
141
|
|
|
142
|
+
/**
|
|
143
|
+
* The first of several wordings, longest first, within the limit. When none
|
|
144
|
+
* fits, the last one — and `warnIfClipped` reports it once it reaches
|
|
145
|
+
* `buildMeta`.
|
|
146
|
+
*/
|
|
147
|
+
function firstFitting(
|
|
148
|
+
field: keyof typeof LENGTH_LIMITS,
|
|
149
|
+
variants: NonEmpty<string>
|
|
150
|
+
): string {
|
|
151
|
+
const { limit } = LENGTH_LIMITS[field];
|
|
152
|
+
// `reduce` to the last, which a non-empty tuple always has; indexing it
|
|
153
|
+
// would type as possibly undefined.
|
|
154
|
+
return (
|
|
155
|
+
variants.find((text) => text.length <= limit) ??
|
|
156
|
+
variants.reduce((_, text) => text)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** A title as the page will print it, suffix included. */
|
|
161
|
+
export function fitTitle(variants: NonEmpty<string>): string {
|
|
162
|
+
return firstFitting("title", variants);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function fitDescription(variants: NonEmpty<string>): string {
|
|
166
|
+
return firstFitting("description", variants);
|
|
167
|
+
}
|
|
168
|
+
|
|
144
169
|
/**
|
|
145
170
|
* Warns about a share image rather than rejecting it.
|
|
146
171
|
*
|
package/src/money.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { NonEmpty } from "./types.ts";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* An ISO 4217 currency code: `"EUR"`, `"RON"`, `"GBP"`.
|
|
3
5
|
*
|
|
@@ -230,7 +232,7 @@ export interface TieredPrice {
|
|
|
230
232
|
* The bands, cheapest quantity first. Never empty — a product with no price
|
|
231
233
|
* at all is an offer of nothing.
|
|
232
234
|
*/
|
|
233
|
-
readonly tiers:
|
|
235
|
+
readonly tiers: NonEmpty<PriceTier>;
|
|
234
236
|
/**
|
|
235
237
|
* What the quantity counts: `"players"`, `"seats"`, `"nights"`.
|
|
236
238
|
*
|
|
@@ -259,9 +261,7 @@ export function tierRange(tier: PriceTier): QuantityRange {
|
|
|
259
261
|
* a caller wants is `formatQuantities` or `quantitiesFor`, which pick between
|
|
260
262
|
* this and `quantitiesOf` by reading the tiers.
|
|
261
263
|
*/
|
|
262
|
-
export function quantityRange(
|
|
263
|
-
tiers: readonly [PriceTier, ...PriceTier[]]
|
|
264
|
-
): QuantityRange {
|
|
264
|
+
export function quantityRange(tiers: NonEmpty<PriceTier>): QuantityRange {
|
|
265
265
|
const first = tierRange(tiers[0]);
|
|
266
266
|
const last = tiers.at(-1);
|
|
267
267
|
return {
|
|
@@ -285,9 +285,7 @@ export function quantityRange(
|
|
|
285
285
|
* different things: a contiguous table is one `QuantitativeValue` with a min and
|
|
286
286
|
* a max, and a table with holes has to enumerate.
|
|
287
287
|
*/
|
|
288
|
-
export function quantitiesOf(
|
|
289
|
-
tiers: readonly [PriceTier, ...PriceTier[]]
|
|
290
|
-
): readonly number[] {
|
|
288
|
+
export function quantitiesOf(tiers: NonEmpty<PriceTier>): readonly number[] {
|
|
291
289
|
const sizes = new Set<number>();
|
|
292
290
|
for (const tier of tiers) {
|
|
293
291
|
const band = tierRange(tier);
|
|
@@ -305,9 +303,7 @@ export function quantitiesOf(
|
|
|
305
303
|
* broken set has to be listed. Derived rather than declared, so it cannot
|
|
306
304
|
* disagree with the tiers it describes.
|
|
307
305
|
*/
|
|
308
|
-
export function isContiguous(
|
|
309
|
-
tiers: readonly [PriceTier, ...PriceTier[]]
|
|
310
|
-
): boolean {
|
|
306
|
+
export function isContiguous(tiers: NonEmpty<PriceTier>): boolean {
|
|
311
307
|
const sizes = quantitiesOf(tiers);
|
|
312
308
|
const first = sizes[0];
|
|
313
309
|
const last = sizes.at(-1);
|
|
@@ -335,7 +331,7 @@ export function isContiguous(
|
|
|
335
331
|
* language and no opinion about dashes.
|
|
336
332
|
*/
|
|
337
333
|
export function formatQuantities(
|
|
338
|
-
tiers:
|
|
334
|
+
tiers: NonEmpty<PriceTier>,
|
|
339
335
|
format: {
|
|
340
336
|
/** Between the ends of a run: `2–6`. */
|
|
341
337
|
readonly between?: string;
|
package/src/types.ts
CHANGED