@brandlab-digital/site-core 2.1.0 → 3.1.0
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/dist/analytics.d.ts +0 -11
- package/dist/analytics.d.ts.map +1 -1
- package/dist/analytics.js +16 -8
- package/dist/analytics.js.map +1 -1
- package/dist/blog.d.ts +11 -0
- package/dist/blog.d.ts.map +1 -1
- package/dist/blog.js +27 -0
- package/dist/blog.js.map +1 -1
- package/dist/business.d.ts +13 -0
- package/dist/business.d.ts.map +1 -1
- package/dist/business.js +19 -8
- package/dist/business.js.map +1 -1
- package/dist/locations.d.ts +89 -2
- package/dist/locations.d.ts.map +1 -1
- package/dist/locations.js +290 -2
- package/dist/locations.js.map +1 -1
- package/dist/metadata.d.ts +18 -5
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +36 -15
- package/dist/metadata.js.map +1 -1
- package/dist/project.d.ts +0 -8
- package/dist/project.d.ts.map +1 -1
- package/dist/project.js +21 -28
- package/dist/project.js.map +1 -1
- package/dist/redirects.d.ts +40 -0
- package/dist/redirects.d.ts.map +1 -0
- package/dist/redirects.js +286 -0
- package/dist/redirects.js.map +1 -0
- package/dist/schema.js +1 -1
- package/dist/schema.js.map +1 -1
- package/dist/seo-page.d.ts +3 -2
- package/dist/seo-page.d.ts.map +1 -1
- package/dist/seo-page.js +3 -9
- package/dist/seo-page.js.map +1 -1
- package/dist/site.d.ts +60 -0
- package/dist/site.d.ts.map +1 -0
- package/dist/site.js +116 -0
- package/dist/site.js.map +1 -0
- package/dist/sitemaps.d.ts +5 -6
- package/dist/sitemaps.d.ts.map +1 -1
- package/dist/sitemaps.js +38 -48
- package/dist/sitemaps.js.map +1 -1
- package/package.json +11 -1
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import { normalizeBrandlabRoutePath } from './routes.js';
|
|
2
|
+
import { isBrandlabHostname } from './site.js';
|
|
3
|
+
const REDIRECT_KEYS = new Set(['source', 'destination', 'status', 'host']);
|
|
4
|
+
const LITERAL_SOURCE_CHARACTER = /[A-Za-z0-9\-._~!$&',;=@/]/;
|
|
5
|
+
const PARAMETER_NAME = /[A-Za-z_][A-Za-z0-9_]*/y;
|
|
6
|
+
const MAX_REDIRECT_PATH_LENGTH = 2_048;
|
|
7
|
+
function describe(value) {
|
|
8
|
+
return typeof value === 'string' ? value : String(value);
|
|
9
|
+
}
|
|
10
|
+
function readGroup(source, start) {
|
|
11
|
+
let depth = 0;
|
|
12
|
+
for (let index = start; index < source.length; index += 1) {
|
|
13
|
+
const character = source[index];
|
|
14
|
+
if (character === '\\')
|
|
15
|
+
return undefined;
|
|
16
|
+
if (character === '(')
|
|
17
|
+
depth += 1;
|
|
18
|
+
if (character === ')') {
|
|
19
|
+
depth -= 1;
|
|
20
|
+
if (depth === 0)
|
|
21
|
+
return { body: source.slice(start + 1, index), end: index + 1 };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
function escapeRegExp(value) {
|
|
27
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parses the subset of Next's path-to-regexp syntax used by redirect tables.
|
|
31
|
+
* The matcher is case-insensitive because Next matches redirect sources
|
|
32
|
+
* case-insensitively; it is used only for loop, chain and shadowing checks.
|
|
33
|
+
*/
|
|
34
|
+
function parseSource(source) {
|
|
35
|
+
if (typeof source !== 'string' ||
|
|
36
|
+
!source.startsWith('/') ||
|
|
37
|
+
source.length > MAX_REDIRECT_PATH_LENGTH ||
|
|
38
|
+
source.includes('//') ||
|
|
39
|
+
/[\s\u0000-\u001f\u007f#\\]/.test(source) ||
|
|
40
|
+
(source !== '/' && source.endsWith('/'))) {
|
|
41
|
+
throw new Error('must be root-relative without whitespace, a fragment or a trailing slash');
|
|
42
|
+
}
|
|
43
|
+
const parameters = new Set();
|
|
44
|
+
let expression = '';
|
|
45
|
+
let pattern = false;
|
|
46
|
+
let unnamed = 0;
|
|
47
|
+
let index = 0;
|
|
48
|
+
while (index < source.length) {
|
|
49
|
+
const character = source[index];
|
|
50
|
+
if (character === ':' || character === '(') {
|
|
51
|
+
pattern = true;
|
|
52
|
+
let name;
|
|
53
|
+
let body = '[^/]+?';
|
|
54
|
+
if (character === ':') {
|
|
55
|
+
PARAMETER_NAME.lastIndex = index + 1;
|
|
56
|
+
const match = PARAMETER_NAME.exec(source);
|
|
57
|
+
if (!match)
|
|
58
|
+
throw new Error(`has an unnamed ":" at position ${index + 1}`);
|
|
59
|
+
name = match[0];
|
|
60
|
+
index = PARAMETER_NAME.lastIndex;
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
name = String(unnamed);
|
|
64
|
+
unnamed += 1;
|
|
65
|
+
}
|
|
66
|
+
if (source[index] === '(') {
|
|
67
|
+
const group = readGroup(source, index);
|
|
68
|
+
if (!group || !group.body)
|
|
69
|
+
throw new Error('has an unbalanced or empty pattern group');
|
|
70
|
+
try {
|
|
71
|
+
new RegExp(group.body);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
throw new Error(`has an invalid pattern group "(${group.body})"`);
|
|
75
|
+
}
|
|
76
|
+
body = group.body;
|
|
77
|
+
index = group.end;
|
|
78
|
+
}
|
|
79
|
+
if (parameters.has(name))
|
|
80
|
+
throw new Error(`repeats parameter "${name}"`);
|
|
81
|
+
parameters.add(name);
|
|
82
|
+
const modifier = source[index];
|
|
83
|
+
const prefixed = expression.endsWith('/');
|
|
84
|
+
const base = prefixed ? expression.slice(0, -1) : expression;
|
|
85
|
+
if (modifier === '?' || modifier === '*' || modifier === '+') {
|
|
86
|
+
index += 1;
|
|
87
|
+
const repeated = `(?:${body})(?:/(?:${body}))*`;
|
|
88
|
+
const segment = modifier === '?' ? `(?:${body})` : repeated;
|
|
89
|
+
const prefix = prefixed ? '/' : '';
|
|
90
|
+
expression = `${base}(?:${prefix}(${segment}))${modifier === '+' ? '' : '?'}`;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
expression += `(${body})`;
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (character === '%') {
|
|
98
|
+
if (!/^%[0-9a-f]{2}$/i.test(source.slice(index, index + 3))) {
|
|
99
|
+
throw new Error('contains invalid percent-encoding');
|
|
100
|
+
}
|
|
101
|
+
expression += escapeRegExp(source.slice(index, index + 3));
|
|
102
|
+
index += 3;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (!LITERAL_SOURCE_CHARACTER.test(character)) {
|
|
106
|
+
throw new Error(`contains "${character}" outside a parameter`);
|
|
107
|
+
}
|
|
108
|
+
expression += escapeRegExp(character);
|
|
109
|
+
index += 1;
|
|
110
|
+
}
|
|
111
|
+
return { pattern, parameters, matcher: new RegExp(`^${expression}$`, 'i') };
|
|
112
|
+
}
|
|
113
|
+
/** Parameters referenced after the origin; an absolute URL's scheme and port are not parameters. */
|
|
114
|
+
function destinationParameters(destination) {
|
|
115
|
+
const reference = destination.replace(/^https:\/\/[^/?#]*/i, '');
|
|
116
|
+
return [...reference.matchAll(/:([A-Za-z_][A-Za-z0-9_]*|\d+)/g)].map((match) => match[1]);
|
|
117
|
+
}
|
|
118
|
+
function parseDestination(destination) {
|
|
119
|
+
if (typeof destination !== 'string' ||
|
|
120
|
+
!destination ||
|
|
121
|
+
destination.length > MAX_REDIRECT_PATH_LENGTH ||
|
|
122
|
+
/[\s\u0000-\u001f\u007f\\]/.test(destination)) {
|
|
123
|
+
throw new Error('must be a root-relative path or absolute HTTPS URL without whitespace');
|
|
124
|
+
}
|
|
125
|
+
const pattern = destinationParameters(destination).length > 0;
|
|
126
|
+
if (destination.startsWith('/')) {
|
|
127
|
+
if (destination.startsWith('//'))
|
|
128
|
+
throw new Error('must not be protocol-relative');
|
|
129
|
+
const path = destination.split(/[?#]/, 1)[0];
|
|
130
|
+
if (path.includes('//') || (path !== '/' && path.endsWith('/'))) {
|
|
131
|
+
throw new Error('must not contain empty segments or end with a slash');
|
|
132
|
+
}
|
|
133
|
+
return { path, pattern };
|
|
134
|
+
}
|
|
135
|
+
let url;
|
|
136
|
+
try {
|
|
137
|
+
url = new URL(destination);
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
throw new Error('must be a root-relative path or absolute HTTPS URL');
|
|
141
|
+
}
|
|
142
|
+
if (url.protocol !== 'https:' || url.username || url.password) {
|
|
143
|
+
throw new Error('must use HTTPS without credentials');
|
|
144
|
+
}
|
|
145
|
+
return { pattern };
|
|
146
|
+
}
|
|
147
|
+
function issue(kind, index, source, detail) {
|
|
148
|
+
return { kind, index, source: describe(source), detail };
|
|
149
|
+
}
|
|
150
|
+
function parseTable(redirects) {
|
|
151
|
+
const issues = [];
|
|
152
|
+
const parsed = [];
|
|
153
|
+
if (!Array.isArray(redirects)) {
|
|
154
|
+
return { issues: [issue('invalid-entry', -1, '', 'The redirect table must be an array.')], parsed };
|
|
155
|
+
}
|
|
156
|
+
const seen = new Map();
|
|
157
|
+
redirects.forEach((redirect, index) => {
|
|
158
|
+
if (!redirect || typeof redirect !== 'object' || Array.isArray(redirect)) {
|
|
159
|
+
issues.push(issue('invalid-entry', index, '', 'Each redirect must be an object.'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const unsupported = Object.keys(redirect).filter((key) => !REDIRECT_KEYS.has(key));
|
|
163
|
+
if (unsupported.length > 0) {
|
|
164
|
+
issues.push(issue('invalid-entry', index, redirect.source, `Unsupported keys: ${unsupported.join(', ')}. Use status: 301 or 308.`));
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
let source;
|
|
168
|
+
try {
|
|
169
|
+
source = parseSource(redirect.source);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
issues.push(issue('invalid-source', index, redirect.source, `Source ${error.message}.`));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
let destination;
|
|
176
|
+
try {
|
|
177
|
+
destination = parseDestination(redirect.destination);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
issues.push(issue('invalid-destination', index, redirect.source, `Destination ${error.message}.`));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (redirect.status !== 301 && redirect.status !== 308) {
|
|
184
|
+
issues.push(issue('invalid-status', index, redirect.source, 'Status must be 301 or 308.'));
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (redirect.host !== undefined && !isBrandlabHostname(redirect.host)) {
|
|
188
|
+
issues.push(issue('invalid-host', index, redirect.source, 'Host must be a lowercase hostname.'));
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const unknownParameter = destinationParameters(redirect.destination).find((name) => !source.parameters.has(name));
|
|
192
|
+
if (unknownParameter) {
|
|
193
|
+
issues.push(issue('unknown-destination-parameter', index, redirect.source, `Destination parameter ":${unknownParameter}" does not appear in the source.`));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
const identity = `${redirect.host ?? ''} ${redirect.source.toLowerCase()}`;
|
|
197
|
+
const previous = seen.get(identity);
|
|
198
|
+
if (previous !== undefined) {
|
|
199
|
+
issues.push(issue('duplicate-source', index, redirect.source, `Source repeats redirects[${previous}] (sources match case-insensitively).`));
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
seen.set(identity, index);
|
|
203
|
+
parsed.push({ index, redirect, source, destination });
|
|
204
|
+
});
|
|
205
|
+
for (const entry of parsed) {
|
|
206
|
+
const path = entry.destination.path;
|
|
207
|
+
if (!path || entry.destination.pattern)
|
|
208
|
+
continue;
|
|
209
|
+
for (const candidate of parsed) {
|
|
210
|
+
if (candidate.redirect.host !== undefined && candidate.redirect.host !== entry.redirect.host) {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (!candidate.source.matcher.test(path))
|
|
214
|
+
continue;
|
|
215
|
+
issues.push(candidate === entry
|
|
216
|
+
? issue('redirect-loop', entry.index, entry.redirect.source, 'The destination matches its own source.')
|
|
217
|
+
: issue('redirect-chain', entry.index, entry.redirect.source, `The destination ${path} is redirected again by redirects[${candidate.index}] (${candidate.redirect.source}); point it at the final page.`));
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return { issues: issues.sort((left, right) => left.index - right.index), parsed };
|
|
222
|
+
}
|
|
223
|
+
function formatIssues(title, issues) {
|
|
224
|
+
const shown = issues.slice(0, 20).map((entry) => `- redirects[${entry.index}] ${entry.source}: ${entry.detail}`);
|
|
225
|
+
const more = issues.length > shown.length ? `\n- …and ${issues.length - shown.length} more` : '';
|
|
226
|
+
return `${title}\n${shown.join('\n')}${more}`;
|
|
227
|
+
}
|
|
228
|
+
/** Validates the single redirect table without inspecting the route catalogue. */
|
|
229
|
+
export function validateRedirects(redirects) {
|
|
230
|
+
return parseTable(redirects).issues;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Declares the project's only redirect table (`src/data/redirects.ts`). Fails
|
|
234
|
+
* on malformed entries, duplicate sources, loops and multi-hop chains.
|
|
235
|
+
*/
|
|
236
|
+
export function defineRedirects(redirects) {
|
|
237
|
+
const issues = validateRedirects(redirects);
|
|
238
|
+
if (issues.length > 0)
|
|
239
|
+
throw new Error(formatIssues('Invalid redirect table:', issues));
|
|
240
|
+
return redirects;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Checks the redirect table against the route catalogue: a redirect must not
|
|
244
|
+
* shadow a catalogued page, and every literal internal destination must be a
|
|
245
|
+
* catalogued page. Pattern destinations and absolute URLs are not resolved.
|
|
246
|
+
*/
|
|
247
|
+
export function validateRedirectsAgainstRoutes(redirects, routes) {
|
|
248
|
+
const { issues, parsed } = parseTable(redirects);
|
|
249
|
+
const routePaths = new Set();
|
|
250
|
+
for (const route of routes) {
|
|
251
|
+
routePaths.add(normalizeBrandlabRoutePath(route.path));
|
|
252
|
+
if (route.canonicalPath)
|
|
253
|
+
routePaths.add(normalizeBrandlabRoutePath(route.canonicalPath));
|
|
254
|
+
}
|
|
255
|
+
for (const entry of parsed) {
|
|
256
|
+
if (entry.redirect.host === undefined) {
|
|
257
|
+
const shadowed = [...routePaths].find((path) => entry.source.matcher.test(path));
|
|
258
|
+
if (shadowed) {
|
|
259
|
+
issues.push(issue('shadows-route', entry.index, entry.redirect.source, `The source redirects the catalogued page ${shadowed}.`));
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
const path = entry.destination.path;
|
|
263
|
+
if (!path || entry.destination.pattern)
|
|
264
|
+
continue;
|
|
265
|
+
let normalized;
|
|
266
|
+
try {
|
|
267
|
+
normalized = normalizeBrandlabRoutePath(path);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
issues.push(issue('invalid-destination', entry.index, entry.redirect.source, `Destination ${path} is not a canonical route path.`));
|
|
271
|
+
continue;
|
|
272
|
+
}
|
|
273
|
+
if (!routePaths.has(normalized)) {
|
|
274
|
+
issues.push(issue('unknown-destination', entry.index, entry.redirect.source, `Destination ${path} is not a catalogued page.`));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return issues.sort((left, right) => left.index - right.index);
|
|
278
|
+
}
|
|
279
|
+
/** Throws with every route-catalogue mismatch listed. */
|
|
280
|
+
export function assertRedirectsAgainstRoutes(redirects, routes) {
|
|
281
|
+
const issues = validateRedirectsAgainstRoutes(redirects, routes);
|
|
282
|
+
if (issues.length > 0) {
|
|
283
|
+
throw new Error(formatIssues('The redirect table does not match the route catalogue:', issues));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
//# sourceMappingURL=redirects.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirects.js","sourceRoot":"","sources":["../src/redirects.ts"],"names":[],"mappings":"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","sourcesContent":["import { normalizeBrandlabRoutePath, type BrandlabRouteCatalog } from './routes.js'\nimport { isBrandlabHostname } from './site.js'\n\nexport type BrandlabRedirectStatus = 301 | 308\n\n/**\n * One permanent redirect. `source` is a root-relative path or Next path\n * pattern (`:slug`, `:path*`) written without a trailing slash; the Next\n * adapter adds the single-hop trailing-slash variant.\n */\nexport interface BrandlabRedirect {\n source: string\n /** Root-relative path (a query string or fragment is allowed) or an absolute HTTPS URL. */\n destination: string\n /** 301 and 308 are both permanent; the existing status is preserved exactly. */\n status: BrandlabRedirectStatus\n /** Applies only when the request Host header matches, for example a retired domain. */\n host?: string\n}\n\nexport type BrandlabRedirectTable = readonly BrandlabRedirect[]\n\nexport type BrandlabRedirectIssueKind =\n | 'invalid-entry'\n | 'invalid-source'\n | 'invalid-destination'\n | 'invalid-status'\n | 'invalid-host'\n | 'unknown-destination-parameter'\n | 'duplicate-source'\n | 'redirect-loop'\n | 'redirect-chain'\n | 'shadows-route'\n | 'unknown-destination'\n\nexport interface BrandlabRedirectIssue {\n kind: BrandlabRedirectIssueKind\n index: number\n source: string\n detail: string\n}\n\nconst REDIRECT_KEYS = new Set(['source', 'destination', 'status', 'host'])\nconst LITERAL_SOURCE_CHARACTER = /[A-Za-z0-9\\-._~!$&',;=@/]/\nconst PARAMETER_NAME = /[A-Za-z_][A-Za-z0-9_]*/y\nconst MAX_REDIRECT_PATH_LENGTH = 2_048\n\ninterface ParsedSource {\n pattern: boolean\n parameters: Set<string>\n matcher: RegExp\n}\n\nfunction describe(value: unknown) {\n return typeof value === 'string' ? value : String(value)\n}\n\nfunction readGroup(source: string, start: number) {\n let depth = 0\n for (let index = start; index < source.length; index += 1) {\n const character = source[index]\n if (character === '\\\\') return undefined\n if (character === '(') depth += 1\n if (character === ')') {\n depth -= 1\n if (depth === 0) return { body: source.slice(start + 1, index), end: index + 1 }\n }\n }\n return undefined\n}\n\nfunction escapeRegExp(value: string) {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n}\n\n/**\n * Parses the subset of Next's path-to-regexp syntax used by redirect tables.\n * The matcher is case-insensitive because Next matches redirect sources\n * case-insensitively; it is used only for loop, chain and shadowing checks.\n */\nfunction parseSource(source: string): ParsedSource {\n if (\n typeof source !== 'string' ||\n !source.startsWith('/') ||\n source.length > MAX_REDIRECT_PATH_LENGTH ||\n source.includes('//') ||\n /[\\s\\u0000-\\u001f\\u007f#\\\\]/.test(source) ||\n (source !== '/' && source.endsWith('/'))\n ) {\n throw new Error('must be root-relative without whitespace, a fragment or a trailing slash')\n }\n const parameters = new Set<string>()\n let expression = ''\n let pattern = false\n let unnamed = 0\n let index = 0\n while (index < source.length) {\n const character = source[index]!\n if (character === ':' || character === '(') {\n pattern = true\n let name: string\n let body = '[^/]+?'\n if (character === ':') {\n PARAMETER_NAME.lastIndex = index + 1\n const match = PARAMETER_NAME.exec(source)\n if (!match) throw new Error(`has an unnamed \":\" at position ${index + 1}`)\n name = match[0]\n index = PARAMETER_NAME.lastIndex\n } else {\n name = String(unnamed)\n unnamed += 1\n }\n if (source[index] === '(') {\n const group = readGroup(source, index)\n if (!group || !group.body) throw new Error('has an unbalanced or empty pattern group')\n try {\n new RegExp(group.body)\n } catch {\n throw new Error(`has an invalid pattern group \"(${group.body})\"`)\n }\n body = group.body\n index = group.end\n }\n if (parameters.has(name)) throw new Error(`repeats parameter \"${name}\"`)\n parameters.add(name)\n const modifier = source[index]\n const prefixed = expression.endsWith('/')\n const base = prefixed ? expression.slice(0, -1) : expression\n if (modifier === '?' || modifier === '*' || modifier === '+') {\n index += 1\n const repeated = `(?:${body})(?:/(?:${body}))*`\n const segment = modifier === '?' ? `(?:${body})` : repeated\n const prefix = prefixed ? '/' : ''\n expression = `${base}(?:${prefix}(${segment}))${modifier === '+' ? '' : '?'}`\n } else {\n expression += `(${body})`\n }\n continue\n }\n if (character === '%') {\n if (!/^%[0-9a-f]{2}$/i.test(source.slice(index, index + 3))) {\n throw new Error('contains invalid percent-encoding')\n }\n expression += escapeRegExp(source.slice(index, index + 3))\n index += 3\n continue\n }\n if (!LITERAL_SOURCE_CHARACTER.test(character)) {\n throw new Error(`contains \"${character}\" outside a parameter`)\n }\n expression += escapeRegExp(character)\n index += 1\n }\n return { pattern, parameters, matcher: new RegExp(`^${expression}$`, 'i') }\n}\n\n/** Parameters referenced after the origin; an absolute URL's scheme and port are not parameters. */\nfunction destinationParameters(destination: string) {\n const reference = destination.replace(/^https:\\/\\/[^/?#]*/i, '')\n return [...reference.matchAll(/:([A-Za-z_][A-Za-z0-9_]*|\\d+)/g)].map((match) => match[1]!)\n}\n\ninterface ParsedDestination {\n /** Root-relative path without query or fragment; undefined for absolute URLs. */\n path?: string\n pattern: boolean\n}\n\nfunction parseDestination(destination: string): ParsedDestination {\n if (\n typeof destination !== 'string' ||\n !destination ||\n destination.length > MAX_REDIRECT_PATH_LENGTH ||\n /[\\s\\u0000-\\u001f\\u007f\\\\]/.test(destination)\n ) {\n throw new Error('must be a root-relative path or absolute HTTPS URL without whitespace')\n }\n const pattern = destinationParameters(destination).length > 0\n if (destination.startsWith('/')) {\n if (destination.startsWith('//')) throw new Error('must not be protocol-relative')\n const path = destination.split(/[?#]/, 1)[0]!\n if (path.includes('//') || (path !== '/' && path.endsWith('/'))) {\n throw new Error('must not contain empty segments or end with a slash')\n }\n return { path, pattern }\n }\n let url: URL\n try {\n url = new URL(destination)\n } catch {\n throw new Error('must be a root-relative path or absolute HTTPS URL')\n }\n if (url.protocol !== 'https:' || url.username || url.password) {\n throw new Error('must use HTTPS without credentials')\n }\n return { pattern }\n}\n\nfunction issue(\n kind: BrandlabRedirectIssueKind,\n index: number,\n source: unknown,\n detail: string,\n): BrandlabRedirectIssue {\n return { kind, index, source: describe(source), detail }\n}\n\ninterface ParsedRedirect {\n index: number\n redirect: BrandlabRedirect\n source: ParsedSource\n destination: ParsedDestination\n}\n\nfunction parseTable(redirects: BrandlabRedirectTable) {\n const issues: BrandlabRedirectIssue[] = []\n const parsed: ParsedRedirect[] = []\n if (!Array.isArray(redirects)) {\n return { issues: [issue('invalid-entry', -1, '', 'The redirect table must be an array.')], parsed }\n }\n const seen = new Map<string, number>()\n redirects.forEach((redirect, index) => {\n if (!redirect || typeof redirect !== 'object' || Array.isArray(redirect)) {\n issues.push(issue('invalid-entry', index, '', 'Each redirect must be an object.'))\n return\n }\n const unsupported = Object.keys(redirect).filter((key) => !REDIRECT_KEYS.has(key))\n if (unsupported.length > 0) {\n issues.push(\n issue('invalid-entry', index, redirect.source, `Unsupported keys: ${unsupported.join(', ')}. Use status: 301 or 308.`),\n )\n return\n }\n let source: ParsedSource\n try {\n source = parseSource(redirect.source)\n } catch (error) {\n issues.push(issue('invalid-source', index, redirect.source, `Source ${(error as Error).message}.`))\n return\n }\n let destination: ParsedDestination\n try {\n destination = parseDestination(redirect.destination)\n } catch (error) {\n issues.push(\n issue('invalid-destination', index, redirect.source, `Destination ${(error as Error).message}.`),\n )\n return\n }\n if (redirect.status !== 301 && redirect.status !== 308) {\n issues.push(issue('invalid-status', index, redirect.source, 'Status must be 301 or 308.'))\n return\n }\n if (redirect.host !== undefined && !isBrandlabHostname(redirect.host)) {\n issues.push(issue('invalid-host', index, redirect.source, 'Host must be a lowercase hostname.'))\n return\n }\n const unknownParameter = destinationParameters(redirect.destination).find(\n (name) => !source.parameters.has(name),\n )\n if (unknownParameter) {\n issues.push(\n issue(\n 'unknown-destination-parameter',\n index,\n redirect.source,\n `Destination parameter \":${unknownParameter}\" does not appear in the source.`,\n ),\n )\n return\n }\n const identity = `${redirect.host ?? ''} ${redirect.source.toLowerCase()}`\n const previous = seen.get(identity)\n if (previous !== undefined) {\n issues.push(\n issue('duplicate-source', index, redirect.source, `Source repeats redirects[${previous}] (sources match case-insensitively).`),\n )\n return\n }\n seen.set(identity, index)\n parsed.push({ index, redirect, source, destination })\n })\n\n for (const entry of parsed) {\n const path = entry.destination.path\n if (!path || entry.destination.pattern) continue\n for (const candidate of parsed) {\n if (candidate.redirect.host !== undefined && candidate.redirect.host !== entry.redirect.host) {\n continue\n }\n if (!candidate.source.matcher.test(path)) continue\n issues.push(\n candidate === entry\n ? issue('redirect-loop', entry.index, entry.redirect.source, 'The destination matches its own source.')\n : issue(\n 'redirect-chain',\n entry.index,\n entry.redirect.source,\n `The destination ${path} is redirected again by redirects[${candidate.index}] (${candidate.redirect.source}); point it at the final page.`,\n ),\n )\n break\n }\n }\n return { issues: issues.sort((left, right) => left.index - right.index), parsed }\n}\n\nfunction formatIssues(title: string, issues: readonly BrandlabRedirectIssue[]) {\n const shown = issues.slice(0, 20).map((entry) =>\n `- redirects[${entry.index}] ${entry.source}: ${entry.detail}`,\n )\n const more = issues.length > shown.length ? `\\n- …and ${issues.length - shown.length} more` : ''\n return `${title}\\n${shown.join('\\n')}${more}`\n}\n\n/** Validates the single redirect table without inspecting the route catalogue. */\nexport function validateRedirects(redirects: BrandlabRedirectTable): BrandlabRedirectIssue[] {\n return parseTable(redirects).issues\n}\n\n/**\n * Declares the project's only redirect table (`src/data/redirects.ts`). Fails\n * on malformed entries, duplicate sources, loops and multi-hop chains.\n */\nexport function defineRedirects<const T extends BrandlabRedirectTable>(redirects: T): T {\n const issues = validateRedirects(redirects)\n if (issues.length > 0) throw new Error(formatIssues('Invalid redirect table:', issues))\n return redirects\n}\n\n/**\n * Checks the redirect table against the route catalogue: a redirect must not\n * shadow a catalogued page, and every literal internal destination must be a\n * catalogued page. Pattern destinations and absolute URLs are not resolved.\n */\nexport function validateRedirectsAgainstRoutes(\n redirects: BrandlabRedirectTable,\n routes: BrandlabRouteCatalog,\n): BrandlabRedirectIssue[] {\n const { issues, parsed } = parseTable(redirects)\n const routePaths = new Set<string>()\n for (const route of routes) {\n routePaths.add(normalizeBrandlabRoutePath(route.path))\n if (route.canonicalPath) routePaths.add(normalizeBrandlabRoutePath(route.canonicalPath))\n }\n for (const entry of parsed) {\n if (entry.redirect.host === undefined) {\n const shadowed = [...routePaths].find((path) => entry.source.matcher.test(path))\n if (shadowed) {\n issues.push(\n issue('shadows-route', entry.index, entry.redirect.source, `The source redirects the catalogued page ${shadowed}.`),\n )\n }\n }\n const path = entry.destination.path\n if (!path || entry.destination.pattern) continue\n let normalized: string\n try {\n normalized = normalizeBrandlabRoutePath(path)\n } catch {\n issues.push(issue('invalid-destination', entry.index, entry.redirect.source, `Destination ${path} is not a canonical route path.`))\n continue\n }\n if (!routePaths.has(normalized)) {\n issues.push(\n issue('unknown-destination', entry.index, entry.redirect.source, `Destination ${path} is not a catalogued page.`),\n )\n }\n }\n return issues.sort((left, right) => left.index - right.index)\n}\n\n/** Throws with every route-catalogue mismatch listed. */\nexport function assertRedirectsAgainstRoutes(\n redirects: BrandlabRedirectTable,\n routes: BrandlabRouteCatalog,\n): void {\n const issues = validateRedirectsAgainstRoutes(redirects, routes)\n if (issues.length > 0) {\n throw new Error(formatIssues('The redirect table does not match the route catalogue:', issues))\n }\n}\n"]}
|
package/dist/schema.js
CHANGED
|
@@ -269,7 +269,7 @@ export function buildLocalBusinessSchema(input) {
|
|
|
269
269
|
name: input.business.tradingName,
|
|
270
270
|
legalName: input.business.legalName,
|
|
271
271
|
url: input.business.canonicalUrl,
|
|
272
|
-
telephone: input.business.phone?.
|
|
272
|
+
telephone: input.business.phone?.schemaTelephone,
|
|
273
273
|
email: input.business.email,
|
|
274
274
|
description: input.description,
|
|
275
275
|
logo: input.logoUrl,
|
package/dist/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BrandlabDerivedBusinessFacts } from './business.js'\nimport type { BreadcrumbItem as SemanticBreadcrumbItem } from './breadcrumbs.js'\nimport { getAbsoluteUrl } from './metadata.js'\n\nexport type Schema<T> = {\n '@context': 'https://schema.org'\n} & T\n\nexport type BreadcrumbItem = SemanticBreadcrumbItem\n\nexport function sanitizeJsonLd(data: unknown): string {\n return JSON.stringify(data)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029')\n}\n\nexport function createSchema<T extends Record<string, unknown>>(data: T): Schema<T> {\n if (Object.hasOwn(data, '@context')) {\n throw new Error('Schema builders own @context; pass only schema node fields.')\n }\n return {\n '@context': 'https://schema.org',\n ...data,\n }\n}\n\nexport function createSchemaGraph(...nodes: unknown[]): Schema<{ '@graph': unknown[] }> {\n const filtered = nodes.filter(Boolean)\n const ids = filtered\n .map((node) =>\n node && typeof node === 'object'\n ? (node as { '@id'?: unknown })['@id']\n : undefined,\n )\n .filter((id): id is string => typeof id === 'string')\n const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index)\n\n if (duplicates.length > 0) {\n throw new Error(\n `Duplicate @id values in one schema graph: ${[...new Set(duplicates)].join(', ')}.`,\n )\n }\n\n return {\n '@context': 'https://schema.org',\n '@graph': filtered,\n }\n}\n\nexport function appendSchemaAnchor(url: string, anchor: string) {\n const cleanAnchor = anchor.replace(/^#+/, '')\n assertNonEmpty(cleanAnchor, 'anchor')\n assertAbsoluteUrl(url, 'url')\n const parsed = new URL(url)\n parsed.search = ''\n parsed.hash = ''\n const normalizedUrl = parsed.pathname === '/' ? parsed.origin + '/' : parsed.href.replace(/\\/$/, '')\n\n return `${normalizedUrl}#${cleanAnchor}`\n}\n\nexport interface SchemaIdRoutes {\n service: (slug: string) => string\n blogPost: (slug: string) => string\n}\n\nexport interface SchemaIdOptions {\n siteUrl: string\n anchors?: {\n business?: string\n website?: string\n service?: string\n article?: string\n }\n routes: SchemaIdRoutes\n}\n\nfunction normalizeSiteUrl(siteUrl: string) {\n assertAbsoluteUrl(siteUrl, 'siteUrl')\n const parsed = new URL(siteUrl)\n if (parsed.pathname !== '/' || parsed.search || parsed.hash) {\n throw new Error('siteUrl must be an absolute HTTPS origin.')\n }\n return parsed.origin\n}\n\nfunction absoluteUrl(siteUrl: string, pathOrUrl: string) {\n if (/^https?:/i.test(pathOrUrl)) {\n assertAbsoluteUrl(pathOrUrl, 'schema URL')\n return pathOrUrl\n }\n return getAbsoluteUrl(normalizeSiteUrl(siteUrl), pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`)\n}\n\nexport function createSchemaIdHelpers(options: SchemaIdOptions) {\n const siteUrl = normalizeSiteUrl(options.siteUrl)\n const anchors = {\n business: '/#business',\n website: '/#website',\n service: '#service',\n article: '#article',\n ...options.anchors,\n }\n\n return {\n businessId: () => `${siteUrl}${anchors.business}`,\n websiteId: () => `${siteUrl}${anchors.website}`,\n serviceIdForSlug: (slug: string) =>\n `${absoluteUrl(siteUrl, options.routes.service(slug))}${anchors.service}`,\n serviceIdForPath: (pathOrUrl: string) =>\n `${absoluteUrl(siteUrl, pathOrUrl)}${anchors.service}`,\n blogPostId: (slug: string) =>\n `${absoluteUrl(siteUrl, options.routes.blogPost(slug))}${anchors.article}`,\n blogPostPageId: (slug: string) => absoluteUrl(siteUrl, options.routes.blogPost(slug)),\n }\n}\n\nexport function buildBreadcrumbListSchema(\n breadcrumbs: BreadcrumbItem[],\n options: { absoluteUrl: (path: string) => string },\n) {\n return {\n '@type': 'BreadcrumbList',\n itemListElement: breadcrumbs.map((crumb, index) => {\n assertNonEmpty(crumb.name, `breadcrumbs[${index}].name`)\n const url = crumb.href ? options.absoluteUrl(crumb.href) : undefined\n if (url) assertAbsoluteUrl(url, `breadcrumbs[${index}].url`)\n return {\n '@type': 'ListItem',\n position: index + 1,\n name: crumb.name,\n item: url\n ? {\n '@type': 'WebPage',\n '@id': url,\n url,\n name: crumb.name,\n }\n : undefined,\n }\n }),\n }\n}\n\nexport type BrandlabSchemaNode = Record<string, unknown> & {\n '@type': string\n '@id'?: string\n}\n\nexport interface BrandlabSchemaExtension {\n node: BrandlabSchemaNode\n rationale: string\n visibleEvidence: string\n}\n\nconst PROHIBITED_SCHEMA_TYPES = new Set([\n 'AggregateRating',\n 'AggregateOffer',\n 'FAQPage',\n 'HowTo',\n 'Offer',\n 'OfferCatalog',\n 'Product',\n 'Review',\n])\nconst PROHIBITED_SCHEMA_PROPERTIES = new Set([\n 'aggregaterating',\n 'hasoffercatalog',\n 'itemoffered',\n 'offer',\n 'offers',\n 'review',\n 'reviews',\n])\n\nfunction compactSchemaTerm(value: string) {\n const direct = /^(?:https?:\\/\\/)?schema\\.org[/#]([^/#]+)$/i.exec(value)\n if (direct) return direct[1]!\n return value.toLowerCase().startsWith('schema:') ? value.slice('schema:'.length) : value\n}\n\nfunction assertNonEmpty(value: string, field: string) {\n if (!value.trim()) throw new Error(`${field} cannot be empty.`)\n}\n\nfunction assertAbsoluteUrl(value: string, field: string) {\n let url: URL\n try {\n url = new URL(value)\n } catch {\n throw new Error(`${field} must be an absolute HTTPS URL.`)\n }\n if (url.protocol !== 'https:' || url.username || url.password) {\n throw new Error(`${field} must be an absolute HTTPS URL.`)\n }\n}\n\nfunction assertSafeSchemaValue(\n value: unknown,\n path: string,\n ancestors = new WeakSet<object>(),\n) {\n if (!value || typeof value !== 'object') return\n\n if (ancestors.has(value)) {\n throw new Error(`Schema payload contains a cyclic value at ${path}.`)\n }\n ancestors.add(value)\n\n try {\n if (Array.isArray(value)) {\n value.forEach((item, index) =>\n assertSafeSchemaValue(item, `${path}[${index}]`, ancestors),\n )\n return\n }\n\n const object = value as Record<string, unknown>\n if (Object.hasOwn(object, '@context')) {\n if (path !== 'schema' || object['@context'] !== 'https://schema.org') {\n throw new Error(\n `Schema @context at ${path} must be the single top-level literal \"https://schema.org\"; custom, local, array and nested contexts are unsupported.`,\n )\n }\n }\n Object.entries(object).forEach(([key, child]) => {\n if (key === '@context') return\n\n if (key.startsWith('@') && !['@graph', '@id', '@type'].includes(key)) {\n throw new Error(`JSON-LD keyword \"${key}\" is unsupported at ${path}.`)\n }\n\n if (key === '@id' && typeof child === 'string') {\n assertAbsoluteUrl(child, `${path}.@id`)\n }\n\n if (key === '@type') {\n const types = Array.isArray(child) ? child : [child]\n types.forEach((type) => {\n const compactType = typeof type === 'string' ? compactSchemaTerm(type) : ''\n if (PROHIBITED_SCHEMA_TYPES.has(compactType)) {\n throw new Error(`Schema type \"${type}\" is prohibited at ${path}.`)\n }\n })\n }\n\n const compactProperty = compactSchemaTerm(key).toLowerCase()\n if (PROHIBITED_SCHEMA_PROPERTIES.has(compactProperty)) {\n throw new Error(`Schema property \"${key}\" is prohibited at ${path}.`)\n }\n\n assertSafeSchemaValue(child, `${path}.${key}`, ancestors)\n })\n } finally {\n ancestors.delete(value)\n }\n}\n\n/** Validates a complete JSON-LD payload before a rendering adapter serializes it. */\nexport function assertProofSafeSchema<T>(schema: T): T {\n if (\n !schema ||\n typeof schema !== 'object' ||\n Array.isArray(schema) ||\n (schema as Record<string, unknown>)['@context'] !== 'https://schema.org'\n ) {\n throw new Error(\n 'A rendered schema payload must use the literal \"https://schema.org\" @context.',\n )\n }\n assertSafeSchemaValue(schema, 'schema')\n return schema\n}\n\nexport function assertSafeSchemaNode(node: BrandlabSchemaNode) {\n assertNonEmpty(node['@type'], '@type')\n if (node['@id']) assertAbsoluteUrl(node['@id'], '@id')\n assertSafeSchemaValue(node, 'schema')\n return node\n}\n\n/** Builds a proof-safe graph from shared builders and reviewed extensions. */\nexport function buildSchemaGraph(options: {\n nodes: readonly (BrandlabSchemaNode | null | false | undefined)[]\n extensions?: readonly BrandlabSchemaExtension[]\n}) {\n const nodes = options.nodes.filter((node): node is BrandlabSchemaNode => Boolean(node))\n nodes.forEach(assertSafeSchemaNode)\n\n const extensionNodes = (options.extensions ?? []).map((extension, index) => {\n assertNonEmpty(extension.rationale, `extensions[${index}].rationale`)\n assertNonEmpty(extension.visibleEvidence, `extensions[${index}].visibleEvidence`)\n return assertSafeSchemaNode(extension.node)\n })\n\n const allNodes = [...nodes, ...extensionNodes]\n const ids = allNodes\n .map((node) => node['@id'])\n .filter((id): id is string => typeof id === 'string')\n const duplicateId = ids.find((id, index) => ids.indexOf(id) !== index)\n if (duplicateId) throw new Error(`Schema graph contains duplicate @id \"${duplicateId}\".`)\n\n return createSchemaGraph(...allNodes) as Schema<{\n '@graph': BrandlabSchemaNode[]\n }>\n}\n\nfunction compact<T extends Record<string, unknown>>(value: T): T {\n return Object.fromEntries(\n Object.entries(value).filter(([, entry]) => entry !== undefined),\n ) as T\n}\n\nexport function buildWebSiteSchema(input: {\n id: string\n url: string\n name: string\n publisherId?: string\n inLanguage?: string\n}) {\n assertNonEmpty(input.name, 'name')\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'WebSite',\n '@id': input.id,\n url: input.url,\n name: input.name,\n publisher: input.publisherId ? { '@id': input.publisherId } : undefined,\n inLanguage: input.inLanguage,\n }),\n )\n}\n\nexport function buildWebPageSchema(input: {\n id: string\n url: string\n name: string\n description: string\n websiteId: string\n breadcrumbId?: string\n mainEntityId?: string\n inLanguage?: string\n}) {\n assertNonEmpty(input.name, 'name')\n assertNonEmpty(input.description, 'description')\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'WebPage',\n '@id': input.id,\n url: input.url,\n name: input.name,\n description: input.description,\n isPartOf: { '@id': input.websiteId },\n breadcrumb: input.breadcrumbId ? { '@id': input.breadcrumbId } : undefined,\n mainEntity: input.mainEntityId ? { '@id': input.mainEntityId } : undefined,\n inLanguage: input.inLanguage,\n }),\n )\n}\n\nexport type BrandlabLocalBusinessSchemaType =\n | 'LocalBusiness'\n | 'ProfessionalService'\n | 'HomeAndConstructionBusiness'\n | 'Electrician'\n | 'GeneralContractor'\n | 'Plumber'\n | 'RoofingContractor'\n\nexport function buildLocalBusinessSchema(input: {\n id: string\n schemaType: BrandlabLocalBusinessSchemaType\n business: BrandlabDerivedBusinessFacts\n description?: string\n logoUrl?: string\n imageUrls?: readonly string[]\n}) {\n assertAbsoluteUrl(input.id, 'id')\n if (input.description !== undefined) assertNonEmpty(input.description, 'description')\n if (input.logoUrl) assertAbsoluteUrl(input.logoUrl, 'logoUrl')\n input.imageUrls?.forEach((url, index) => assertAbsoluteUrl(url, `imageUrls[${index}]`))\n\n return assertSafeSchemaNode(\n compact({\n '@type': input.schemaType,\n '@id': input.id,\n name: input.business.tradingName,\n legalName: input.business.legalName,\n url: input.business.canonicalUrl,\n telephone: input.business.phone?.e164,\n email: input.business.email,\n description: input.description,\n logo: input.logoUrl,\n image: input.imageUrls && input.imageUrls.length > 0 ? input.imageUrls : undefined,\n address: input.business.publicAddress\n ? {\n '@type': 'PostalAddress',\n ...input.business.publicAddress,\n }\n : undefined,\n areaServed:\n input.business.serviceAreas.length > 0\n ? input.business.serviceAreas.map((area) => ({\n '@type':\n area.kind === 'city'\n ? 'City'\n : area.kind === 'state'\n ? 'State'\n : area.kind === 'country'\n ? 'Country'\n : 'Place',\n name: area.name,\n }))\n : undefined,\n openingHoursSpecification:\n input.business.openingHours.length > 0\n ? input.business.openingHours.map((period) => ({\n '@type': 'OpeningHoursSpecification',\n dayOfWeek: period.days,\n opens: period.opens,\n closes: period.closes,\n }))\n : undefined,\n taxID: input.business.abn,\n sameAs: input.business.sameAs.length > 0 ? input.business.sameAs : undefined,\n }),\n )\n}\n\nexport function buildServiceSchema(input: {\n id: string\n url: string\n name: string\n description: string\n serviceType: string\n providerId: string\n areaServed?: readonly string[]\n}) {\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n assertNonEmpty(input.name, 'name')\n assertNonEmpty(input.description, 'description')\n assertNonEmpty(input.serviceType, 'serviceType')\n input.areaServed?.forEach((area, index) => assertNonEmpty(area, `areaServed[${index}]`))\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'Service',\n '@id': input.id,\n url: input.url,\n name: input.name,\n description: input.description,\n serviceType: input.serviceType,\n provider: { '@id': input.providerId },\n areaServed:\n input.areaServed && input.areaServed.length > 0\n ? input.areaServed.map((name) => ({ '@type': 'Place', name }))\n : undefined,\n }),\n )\n}\n\nexport function buildLocationSchema(input: {\n id: string\n url: string\n name: string\n description: string\n websiteId: string\n place: {\n name: string\n containedInPlace?: string\n }\n businessId?: string\n}) {\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n assertNonEmpty(input.name, 'name')\n assertNonEmpty(input.description, 'description')\n assertNonEmpty(input.place.name, 'place.name')\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'WebPage',\n '@id': input.id,\n url: input.url,\n name: input.name,\n description: input.description,\n isPartOf: { '@id': input.websiteId },\n about: compact({\n '@type': 'Place',\n name: input.place.name,\n containedInPlace: input.place.containedInPlace\n ? { '@type': 'Place', name: input.place.containedInPlace }\n : undefined,\n }),\n mainEntity: input.businessId ? { '@id': input.businessId } : undefined,\n }),\n )\n}\n\nexport function buildBlogPostingSchema(input: {\n id: string\n url: string\n headline: string\n description: string\n datePublished: string\n dateModified?: string\n authorNames: readonly string[]\n publisherId: string\n imageUrls?: readonly string[]\n mainEntityOfPageId?: string\n}) {\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n assertNonEmpty(input.headline, 'headline')\n assertNonEmpty(input.description, 'description')\n if (Number.isNaN(Date.parse(input.datePublished))) throw new Error('datePublished must be valid.')\n if (input.dateModified && Number.isNaN(Date.parse(input.dateModified))) {\n throw new Error('dateModified must be valid.')\n }\n if (\n input.dateModified &&\n Date.parse(input.dateModified) < Date.parse(input.datePublished)\n ) {\n throw new Error('dateModified cannot be earlier than datePublished.')\n }\n if (input.authorNames.length === 0) throw new Error('authorNames cannot be empty.')\n input.authorNames.forEach((name, index) => assertNonEmpty(name, `authorNames[${index}]`))\n input.imageUrls?.forEach((url, index) => assertAbsoluteUrl(url, `imageUrls[${index}]`))\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'BlogPosting',\n '@id': input.id,\n url: input.url,\n headline: input.headline,\n description: input.description,\n datePublished: input.datePublished,\n dateModified: input.dateModified ?? input.datePublished,\n author: input.authorNames.map((name) => ({ '@type': 'Person', name })),\n publisher: { '@id': input.publisherId },\n image: input.imageUrls,\n mainEntityOfPage: input.mainEntityOfPageId\n ? { '@id': input.mainEntityOfPageId }\n : { '@id': input.url },\n }),\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BrandlabDerivedBusinessFacts } from './business.js'\nimport type { BreadcrumbItem as SemanticBreadcrumbItem } from './breadcrumbs.js'\nimport { getAbsoluteUrl } from './metadata.js'\n\nexport type Schema<T> = {\n '@context': 'https://schema.org'\n} & T\n\nexport type BreadcrumbItem = SemanticBreadcrumbItem\n\nexport function sanitizeJsonLd(data: unknown): string {\n return JSON.stringify(data)\n .replace(/</g, '\\\\u003c')\n .replace(/>/g, '\\\\u003e')\n .replace(/&/g, '\\\\u0026')\n .replace(/\\u2028/g, '\\\\u2028')\n .replace(/\\u2029/g, '\\\\u2029')\n}\n\nexport function createSchema<T extends Record<string, unknown>>(data: T): Schema<T> {\n if (Object.hasOwn(data, '@context')) {\n throw new Error('Schema builders own @context; pass only schema node fields.')\n }\n return {\n '@context': 'https://schema.org',\n ...data,\n }\n}\n\nexport function createSchemaGraph(...nodes: unknown[]): Schema<{ '@graph': unknown[] }> {\n const filtered = nodes.filter(Boolean)\n const ids = filtered\n .map((node) =>\n node && typeof node === 'object'\n ? (node as { '@id'?: unknown })['@id']\n : undefined,\n )\n .filter((id): id is string => typeof id === 'string')\n const duplicates = ids.filter((id, index) => ids.indexOf(id) !== index)\n\n if (duplicates.length > 0) {\n throw new Error(\n `Duplicate @id values in one schema graph: ${[...new Set(duplicates)].join(', ')}.`,\n )\n }\n\n return {\n '@context': 'https://schema.org',\n '@graph': filtered,\n }\n}\n\nexport function appendSchemaAnchor(url: string, anchor: string) {\n const cleanAnchor = anchor.replace(/^#+/, '')\n assertNonEmpty(cleanAnchor, 'anchor')\n assertAbsoluteUrl(url, 'url')\n const parsed = new URL(url)\n parsed.search = ''\n parsed.hash = ''\n const normalizedUrl = parsed.pathname === '/' ? parsed.origin + '/' : parsed.href.replace(/\\/$/, '')\n\n return `${normalizedUrl}#${cleanAnchor}`\n}\n\nexport interface SchemaIdRoutes {\n service: (slug: string) => string\n blogPost: (slug: string) => string\n}\n\nexport interface SchemaIdOptions {\n siteUrl: string\n anchors?: {\n business?: string\n website?: string\n service?: string\n article?: string\n }\n routes: SchemaIdRoutes\n}\n\nfunction normalizeSiteUrl(siteUrl: string) {\n assertAbsoluteUrl(siteUrl, 'siteUrl')\n const parsed = new URL(siteUrl)\n if (parsed.pathname !== '/' || parsed.search || parsed.hash) {\n throw new Error('siteUrl must be an absolute HTTPS origin.')\n }\n return parsed.origin\n}\n\nfunction absoluteUrl(siteUrl: string, pathOrUrl: string) {\n if (/^https?:/i.test(pathOrUrl)) {\n assertAbsoluteUrl(pathOrUrl, 'schema URL')\n return pathOrUrl\n }\n return getAbsoluteUrl(normalizeSiteUrl(siteUrl), pathOrUrl.startsWith('/') ? pathOrUrl : `/${pathOrUrl}`)\n}\n\nexport function createSchemaIdHelpers(options: SchemaIdOptions) {\n const siteUrl = normalizeSiteUrl(options.siteUrl)\n const anchors = {\n business: '/#business',\n website: '/#website',\n service: '#service',\n article: '#article',\n ...options.anchors,\n }\n\n return {\n businessId: () => `${siteUrl}${anchors.business}`,\n websiteId: () => `${siteUrl}${anchors.website}`,\n serviceIdForSlug: (slug: string) =>\n `${absoluteUrl(siteUrl, options.routes.service(slug))}${anchors.service}`,\n serviceIdForPath: (pathOrUrl: string) =>\n `${absoluteUrl(siteUrl, pathOrUrl)}${anchors.service}`,\n blogPostId: (slug: string) =>\n `${absoluteUrl(siteUrl, options.routes.blogPost(slug))}${anchors.article}`,\n blogPostPageId: (slug: string) => absoluteUrl(siteUrl, options.routes.blogPost(slug)),\n }\n}\n\nexport function buildBreadcrumbListSchema(\n breadcrumbs: BreadcrumbItem[],\n options: { absoluteUrl: (path: string) => string },\n) {\n return {\n '@type': 'BreadcrumbList',\n itemListElement: breadcrumbs.map((crumb, index) => {\n assertNonEmpty(crumb.name, `breadcrumbs[${index}].name`)\n const url = crumb.href ? options.absoluteUrl(crumb.href) : undefined\n if (url) assertAbsoluteUrl(url, `breadcrumbs[${index}].url`)\n return {\n '@type': 'ListItem',\n position: index + 1,\n name: crumb.name,\n item: url\n ? {\n '@type': 'WebPage',\n '@id': url,\n url,\n name: crumb.name,\n }\n : undefined,\n }\n }),\n }\n}\n\nexport type BrandlabSchemaNode = Record<string, unknown> & {\n '@type': string\n '@id'?: string\n}\n\nexport interface BrandlabSchemaExtension {\n node: BrandlabSchemaNode\n rationale: string\n visibleEvidence: string\n}\n\nconst PROHIBITED_SCHEMA_TYPES = new Set([\n 'AggregateRating',\n 'AggregateOffer',\n 'FAQPage',\n 'HowTo',\n 'Offer',\n 'OfferCatalog',\n 'Product',\n 'Review',\n])\nconst PROHIBITED_SCHEMA_PROPERTIES = new Set([\n 'aggregaterating',\n 'hasoffercatalog',\n 'itemoffered',\n 'offer',\n 'offers',\n 'review',\n 'reviews',\n])\n\nfunction compactSchemaTerm(value: string) {\n const direct = /^(?:https?:\\/\\/)?schema\\.org[/#]([^/#]+)$/i.exec(value)\n if (direct) return direct[1]!\n return value.toLowerCase().startsWith('schema:') ? value.slice('schema:'.length) : value\n}\n\nfunction assertNonEmpty(value: string, field: string) {\n if (!value.trim()) throw new Error(`${field} cannot be empty.`)\n}\n\nfunction assertAbsoluteUrl(value: string, field: string) {\n let url: URL\n try {\n url = new URL(value)\n } catch {\n throw new Error(`${field} must be an absolute HTTPS URL.`)\n }\n if (url.protocol !== 'https:' || url.username || url.password) {\n throw new Error(`${field} must be an absolute HTTPS URL.`)\n }\n}\n\nfunction assertSafeSchemaValue(\n value: unknown,\n path: string,\n ancestors = new WeakSet<object>(),\n) {\n if (!value || typeof value !== 'object') return\n\n if (ancestors.has(value)) {\n throw new Error(`Schema payload contains a cyclic value at ${path}.`)\n }\n ancestors.add(value)\n\n try {\n if (Array.isArray(value)) {\n value.forEach((item, index) =>\n assertSafeSchemaValue(item, `${path}[${index}]`, ancestors),\n )\n return\n }\n\n const object = value as Record<string, unknown>\n if (Object.hasOwn(object, '@context')) {\n if (path !== 'schema' || object['@context'] !== 'https://schema.org') {\n throw new Error(\n `Schema @context at ${path} must be the single top-level literal \"https://schema.org\"; custom, local, array and nested contexts are unsupported.`,\n )\n }\n }\n Object.entries(object).forEach(([key, child]) => {\n if (key === '@context') return\n\n if (key.startsWith('@') && !['@graph', '@id', '@type'].includes(key)) {\n throw new Error(`JSON-LD keyword \"${key}\" is unsupported at ${path}.`)\n }\n\n if (key === '@id' && typeof child === 'string') {\n assertAbsoluteUrl(child, `${path}.@id`)\n }\n\n if (key === '@type') {\n const types = Array.isArray(child) ? child : [child]\n types.forEach((type) => {\n const compactType = typeof type === 'string' ? compactSchemaTerm(type) : ''\n if (PROHIBITED_SCHEMA_TYPES.has(compactType)) {\n throw new Error(`Schema type \"${type}\" is prohibited at ${path}.`)\n }\n })\n }\n\n const compactProperty = compactSchemaTerm(key).toLowerCase()\n if (PROHIBITED_SCHEMA_PROPERTIES.has(compactProperty)) {\n throw new Error(`Schema property \"${key}\" is prohibited at ${path}.`)\n }\n\n assertSafeSchemaValue(child, `${path}.${key}`, ancestors)\n })\n } finally {\n ancestors.delete(value)\n }\n}\n\n/** Validates a complete JSON-LD payload before a rendering adapter serializes it. */\nexport function assertProofSafeSchema<T>(schema: T): T {\n if (\n !schema ||\n typeof schema !== 'object' ||\n Array.isArray(schema) ||\n (schema as Record<string, unknown>)['@context'] !== 'https://schema.org'\n ) {\n throw new Error(\n 'A rendered schema payload must use the literal \"https://schema.org\" @context.',\n )\n }\n assertSafeSchemaValue(schema, 'schema')\n return schema\n}\n\nexport function assertSafeSchemaNode(node: BrandlabSchemaNode) {\n assertNonEmpty(node['@type'], '@type')\n if (node['@id']) assertAbsoluteUrl(node['@id'], '@id')\n assertSafeSchemaValue(node, 'schema')\n return node\n}\n\n/** Builds a proof-safe graph from shared builders and reviewed extensions. */\nexport function buildSchemaGraph(options: {\n nodes: readonly (BrandlabSchemaNode | null | false | undefined)[]\n extensions?: readonly BrandlabSchemaExtension[]\n}) {\n const nodes = options.nodes.filter((node): node is BrandlabSchemaNode => Boolean(node))\n nodes.forEach(assertSafeSchemaNode)\n\n const extensionNodes = (options.extensions ?? []).map((extension, index) => {\n assertNonEmpty(extension.rationale, `extensions[${index}].rationale`)\n assertNonEmpty(extension.visibleEvidence, `extensions[${index}].visibleEvidence`)\n return assertSafeSchemaNode(extension.node)\n })\n\n const allNodes = [...nodes, ...extensionNodes]\n const ids = allNodes\n .map((node) => node['@id'])\n .filter((id): id is string => typeof id === 'string')\n const duplicateId = ids.find((id, index) => ids.indexOf(id) !== index)\n if (duplicateId) throw new Error(`Schema graph contains duplicate @id \"${duplicateId}\".`)\n\n return createSchemaGraph(...allNodes) as Schema<{\n '@graph': BrandlabSchemaNode[]\n }>\n}\n\nfunction compact<T extends Record<string, unknown>>(value: T): T {\n return Object.fromEntries(\n Object.entries(value).filter(([, entry]) => entry !== undefined),\n ) as T\n}\n\nexport function buildWebSiteSchema(input: {\n id: string\n url: string\n name: string\n publisherId?: string\n inLanguage?: string\n}) {\n assertNonEmpty(input.name, 'name')\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'WebSite',\n '@id': input.id,\n url: input.url,\n name: input.name,\n publisher: input.publisherId ? { '@id': input.publisherId } : undefined,\n inLanguage: input.inLanguage,\n }),\n )\n}\n\nexport function buildWebPageSchema(input: {\n id: string\n url: string\n name: string\n description: string\n websiteId: string\n breadcrumbId?: string\n mainEntityId?: string\n inLanguage?: string\n}) {\n assertNonEmpty(input.name, 'name')\n assertNonEmpty(input.description, 'description')\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'WebPage',\n '@id': input.id,\n url: input.url,\n name: input.name,\n description: input.description,\n isPartOf: { '@id': input.websiteId },\n breadcrumb: input.breadcrumbId ? { '@id': input.breadcrumbId } : undefined,\n mainEntity: input.mainEntityId ? { '@id': input.mainEntityId } : undefined,\n inLanguage: input.inLanguage,\n }),\n )\n}\n\nexport type BrandlabLocalBusinessSchemaType =\n | 'LocalBusiness'\n | 'ProfessionalService'\n | 'HomeAndConstructionBusiness'\n | 'Electrician'\n | 'GeneralContractor'\n | 'Plumber'\n | 'RoofingContractor'\n\nexport function buildLocalBusinessSchema(input: {\n id: string\n schemaType: BrandlabLocalBusinessSchemaType\n business: BrandlabDerivedBusinessFacts\n description?: string\n logoUrl?: string\n imageUrls?: readonly string[]\n}) {\n assertAbsoluteUrl(input.id, 'id')\n if (input.description !== undefined) assertNonEmpty(input.description, 'description')\n if (input.logoUrl) assertAbsoluteUrl(input.logoUrl, 'logoUrl')\n input.imageUrls?.forEach((url, index) => assertAbsoluteUrl(url, `imageUrls[${index}]`))\n\n return assertSafeSchemaNode(\n compact({\n '@type': input.schemaType,\n '@id': input.id,\n name: input.business.tradingName,\n legalName: input.business.legalName,\n url: input.business.canonicalUrl,\n telephone: input.business.phone?.schemaTelephone,\n email: input.business.email,\n description: input.description,\n logo: input.logoUrl,\n image: input.imageUrls && input.imageUrls.length > 0 ? input.imageUrls : undefined,\n address: input.business.publicAddress\n ? {\n '@type': 'PostalAddress',\n ...input.business.publicAddress,\n }\n : undefined,\n areaServed:\n input.business.serviceAreas.length > 0\n ? input.business.serviceAreas.map((area) => ({\n '@type':\n area.kind === 'city'\n ? 'City'\n : area.kind === 'state'\n ? 'State'\n : area.kind === 'country'\n ? 'Country'\n : 'Place',\n name: area.name,\n }))\n : undefined,\n openingHoursSpecification:\n input.business.openingHours.length > 0\n ? input.business.openingHours.map((period) => ({\n '@type': 'OpeningHoursSpecification',\n dayOfWeek: period.days,\n opens: period.opens,\n closes: period.closes,\n }))\n : undefined,\n taxID: input.business.abn,\n sameAs: input.business.sameAs.length > 0 ? input.business.sameAs : undefined,\n }),\n )\n}\n\nexport function buildServiceSchema(input: {\n id: string\n url: string\n name: string\n description: string\n serviceType: string\n providerId: string\n areaServed?: readonly string[]\n}) {\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n assertNonEmpty(input.name, 'name')\n assertNonEmpty(input.description, 'description')\n assertNonEmpty(input.serviceType, 'serviceType')\n input.areaServed?.forEach((area, index) => assertNonEmpty(area, `areaServed[${index}]`))\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'Service',\n '@id': input.id,\n url: input.url,\n name: input.name,\n description: input.description,\n serviceType: input.serviceType,\n provider: { '@id': input.providerId },\n areaServed:\n input.areaServed && input.areaServed.length > 0\n ? input.areaServed.map((name) => ({ '@type': 'Place', name }))\n : undefined,\n }),\n )\n}\n\nexport function buildLocationSchema(input: {\n id: string\n url: string\n name: string\n description: string\n websiteId: string\n place: {\n name: string\n containedInPlace?: string\n }\n businessId?: string\n}) {\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n assertNonEmpty(input.name, 'name')\n assertNonEmpty(input.description, 'description')\n assertNonEmpty(input.place.name, 'place.name')\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'WebPage',\n '@id': input.id,\n url: input.url,\n name: input.name,\n description: input.description,\n isPartOf: { '@id': input.websiteId },\n about: compact({\n '@type': 'Place',\n name: input.place.name,\n containedInPlace: input.place.containedInPlace\n ? { '@type': 'Place', name: input.place.containedInPlace }\n : undefined,\n }),\n mainEntity: input.businessId ? { '@id': input.businessId } : undefined,\n }),\n )\n}\n\nexport function buildBlogPostingSchema(input: {\n id: string\n url: string\n headline: string\n description: string\n datePublished: string\n dateModified?: string\n authorNames: readonly string[]\n publisherId: string\n imageUrls?: readonly string[]\n mainEntityOfPageId?: string\n}) {\n assertAbsoluteUrl(input.id, 'id')\n assertAbsoluteUrl(input.url, 'url')\n assertNonEmpty(input.headline, 'headline')\n assertNonEmpty(input.description, 'description')\n if (Number.isNaN(Date.parse(input.datePublished))) throw new Error('datePublished must be valid.')\n if (input.dateModified && Number.isNaN(Date.parse(input.dateModified))) {\n throw new Error('dateModified must be valid.')\n }\n if (\n input.dateModified &&\n Date.parse(input.dateModified) < Date.parse(input.datePublished)\n ) {\n throw new Error('dateModified cannot be earlier than datePublished.')\n }\n if (input.authorNames.length === 0) throw new Error('authorNames cannot be empty.')\n input.authorNames.forEach((name, index) => assertNonEmpty(name, `authorNames[${index}]`))\n input.imageUrls?.forEach((url, index) => assertAbsoluteUrl(url, `imageUrls[${index}]`))\n\n return assertSafeSchemaNode(\n compact({\n '@type': 'BlogPosting',\n '@id': input.id,\n url: input.url,\n headline: input.headline,\n description: input.description,\n datePublished: input.datePublished,\n dateModified: input.dateModified ?? input.datePublished,\n author: input.authorNames.map((name) => ({ '@type': 'Person', name })),\n publisher: { '@id': input.publisherId },\n image: input.imageUrls,\n mainEntityOfPage: input.mainEntityOfPageId\n ? { '@id': input.mainEntityOfPageId }\n : { '@id': input.url },\n }),\n )\n}\n"]}
|
package/dist/seo-page.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BrandlabMetadataKeywords, type BrandlabMetadataTitleMode, type BrandlabSocialImage } from './metadata.js';
|
|
2
2
|
export interface BrandlabSeoPage {
|
|
3
3
|
routeId: string;
|
|
4
4
|
title: string;
|
|
5
5
|
description: string;
|
|
6
6
|
titleMode?: BrandlabMetadataTitleMode;
|
|
7
|
-
|
|
7
|
+
/** A comma-separated string renders exactly as given; a list renders joined with commas. */
|
|
8
|
+
keywords?: BrandlabMetadataKeywords;
|
|
8
9
|
socialImage?: BrandlabSocialImage;
|
|
9
10
|
article?: {
|
|
10
11
|
publishedTime: string;
|
package/dist/seo-page.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seo-page.d.ts","sourceRoot":"","sources":["../src/seo-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"seo-page.d.ts","sourceRoot":"","sources":["../src/seo-page.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAC9B,KAAK,mBAAmB,EACzB,MAAM,eAAe,CAAA;AAEtB,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,yBAAyB,CAAA;IACrC,4FAA4F;IAC5F,QAAQ,CAAC,EAAE,wBAAwB,CAAA;IACnC,WAAW,CAAC,EAAE,mBAAmB,CAAA;IACjC,OAAO,CAAC,EAAE;QACR,aAAa,EAAE,MAAM,CAAA;QACrB,YAAY,CAAC,EAAE,MAAM,CAAA;QACrB,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;KAC3B,CAAA;CACF;AAuBD,wBAAgB,aAAa,CAAC,KAAK,CAAC,CAAC,SAAS,eAAe,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CA2BzE"}
|
package/dist/seo-page.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertBrandlabKeywords, } from './metadata.js';
|
|
1
2
|
function assertText(value, field, maximum) {
|
|
2
3
|
if (typeof value !== 'string' ||
|
|
3
4
|
!value.trim() ||
|
|
@@ -20,15 +21,8 @@ export function defineSeoPage(page) {
|
|
|
20
21
|
}
|
|
21
22
|
assertText(page.title, 'title', 160);
|
|
22
23
|
assertText(page.description, 'description', 320);
|
|
23
|
-
if (page.keywords
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
page.keywords?.forEach((keyword, index) => assertText(keyword, `keywords[${index}]`, 100));
|
|
27
|
-
if (page.keywords &&
|
|
28
|
-
new Set(page.keywords.map((keyword) => keyword.toLowerCase())).size !==
|
|
29
|
-
page.keywords.length) {
|
|
30
|
-
throw new Error('keywords must not contain duplicate values.');
|
|
31
|
-
}
|
|
24
|
+
if (page.keywords !== undefined)
|
|
25
|
+
assertBrandlabKeywords(page.keywords);
|
|
32
26
|
if (page.article) {
|
|
33
27
|
assertDate(page.article.publishedTime, 'article.publishedTime');
|
|
34
28
|
if (page.article.modifiedTime)
|
package/dist/seo-page.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"seo-page.js","sourceRoot":"","sources":["../src/seo-page.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"seo-page.js","sourceRoot":"","sources":["../src/seo-page.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,GAIvB,MAAM,eAAe,CAAA;AAiBtB,SAAS,UAAU,CAAC,KAAc,EAAE,KAAa,EAAE,OAAe;IAChE,IACE,OAAO,KAAK,KAAK,QAAQ;QACzB,CAAC,KAAK,CAAC,IAAI,EAAE;QACb,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK;QACtB,KAAK,CAAC,MAAM,GAAG,OAAO;QACtB,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,EACnC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,8CAA8C,CAAC,CAAA;IACzE,CAAC;AACH,CAAC;AAED,SAAS,UAAU,CAAC,KAAa,EAAE,KAAa;IAC9C,IACE,CAAC,yDAAyD,CAAC,IAAI,CAAC,KAAK,CAAC;QACtE,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAC/B,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,wCAAwC,CAAC,CAAA;IACnE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAkC,IAAO;IACpE,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;IACxC,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;IAC1D,CAAC;IACD,UAAU,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;IACpC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,EAAE,GAAG,CAAC,CAAA;IAChD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;QAAE,sBAAsB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAEtE,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QACjB,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,EAAE,uBAAuB,CAAC,CAAA;QAC/D,IAAI,IAAI,CAAC,OAAO,CAAC,YAAY;YAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,EAAE,sBAAsB,CAAC,CAAA;QAC5F,IACE,IAAI,CAAC,OAAO,CAAC,YAAY;YACzB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,EAC9E,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,oEAAoE,CAAC,CAAA;QACvF,CAAC;QACD,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAA;QAC3E,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAC7C,UAAU,CAAC,MAAM,EAAE,mBAAmB,KAAK,GAAG,EAAE,GAAG,CAAC,CACrD,CAAA;IACH,CAAC;IAED,OAAO,IAAI,CAAA;AACb,CAAC","sourcesContent":["import {\n assertBrandlabKeywords,\n type BrandlabMetadataKeywords,\n type BrandlabMetadataTitleMode,\n type BrandlabSocialImage,\n} from './metadata.js'\n\nexport interface BrandlabSeoPage {\n routeId: string\n title: string\n description: string\n titleMode?: BrandlabMetadataTitleMode\n /** A comma-separated string renders exactly as given; a list renders joined with commas. */\n keywords?: BrandlabMetadataKeywords\n socialImage?: BrandlabSocialImage\n article?: {\n publishedTime: string\n modifiedTime?: string\n authors: readonly string[]\n }\n}\n\nfunction assertText(value: unknown, field: string, maximum: number) {\n if (\n typeof value !== 'string' ||\n !value.trim() ||\n value.trim() !== value ||\n value.length > maximum ||\n /[\\u0000-\\u001f\\u007f]/.test(value)\n ) {\n throw new Error(`${field} must be bounded, trimmed, single-line text.`)\n }\n}\n\nfunction assertDate(value: string, field: string) {\n if (\n !/^\\d{4}-\\d{2}-\\d{2}(?:T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{3})?Z)?$/.test(value) ||\n Number.isNaN(Date.parse(value))\n ) {\n throw new Error(`${field} must be an ISO date or UTC timestamp.`)\n }\n}\n\nexport function defineSeoPage<const T extends BrandlabSeoPage>(page: T): T {\n assertText(page.routeId, 'routeId', 100)\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(page.routeId)) {\n throw new Error('routeId must be lowercase kebab-case.')\n }\n assertText(page.title, 'title', 160)\n assertText(page.description, 'description', 320)\n if (page.keywords !== undefined) assertBrandlabKeywords(page.keywords)\n\n if (page.article) {\n assertDate(page.article.publishedTime, 'article.publishedTime')\n if (page.article.modifiedTime) assertDate(page.article.modifiedTime, 'article.modifiedTime')\n if (\n page.article.modifiedTime &&\n Date.parse(page.article.modifiedTime) < Date.parse(page.article.publishedTime)\n ) {\n throw new Error('article.modifiedTime cannot be earlier than article.publishedTime.')\n }\n if (page.article.authors.length === 0) {\n throw new Error('article.authors requires at least one reviewed author.')\n }\n page.article.authors.forEach((author, index) =>\n assertText(author, `article.authors[${index}]`, 160),\n )\n }\n\n return page\n}\n"]}
|