@getquick/content 0.1.0 → 0.3.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/README.md +155 -6
- package/dist/consent-astro.js +1 -1
- package/dist/consent.js +1 -1
- package/dist/dist-D4-CMkrf.js +7047 -0
- package/dist/index.d.ts +20 -4
- package/dist/index.js +24 -5
- package/dist/metadata-content.d.ts +36 -0
- package/dist/metadata-content.js +51 -0
- package/dist/metadata-testing.d.ts +34 -0
- package/dist/metadata-testing.js +142 -0
- package/dist/metadata.d.ts +94 -0
- package/dist/metadata.js +218 -0
- package/dist/server.d.ts +2 -2
- package/dist/server.js +943 -58
- package/dist/{formatting-wOk2jz6J.js → site-language-cBP8SFrS.js} +302 -50
- package/dist/{version-Dn6VLZJO.js → version-D_Ao31D3.js} +1 -1
- package/dist/wordpress-D_HG2rEt.d.ts +606 -0
- package/package.json +13 -1
- package/dist/wordpress-CPL40-vq.d.ts +0 -318
package/dist/metadata.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
//#region frontend/src/metadata.ts
|
|
2
|
+
/** Scoped OG-1 candidate contract; adoption and public asset verification are separate gates. */
|
|
3
|
+
const metadataBaseline = "OG-1";
|
|
4
|
+
/** Inputs are plain text. Source-specific HTML/entity decoding belongs in the mapper. */
|
|
5
|
+
function normalizeMetadataText(value) {
|
|
6
|
+
return value.replace(/\s+/gu, " ").trim();
|
|
7
|
+
}
|
|
8
|
+
function resolveMetadata(input) {
|
|
9
|
+
const diagnostics = [];
|
|
10
|
+
const fail = (field, message) => diagnostics.push({
|
|
11
|
+
code: "invalid-metadata",
|
|
12
|
+
field,
|
|
13
|
+
message
|
|
14
|
+
});
|
|
15
|
+
const language = input.language ?? input.site.language;
|
|
16
|
+
const text = (value, field) => {
|
|
17
|
+
const result = normalizeMetadataText(value ?? "");
|
|
18
|
+
if (!result) fail(field, "Expected nonempty decoded plain text");
|
|
19
|
+
if (result.includes("\0") || /[\uD800-\uDFFF]/u.test(result)) fail(field, "Text contains NUL or unpaired UTF-16 surrogate and cannot survive HTML/UTF-8 serialization");
|
|
20
|
+
return result;
|
|
21
|
+
};
|
|
22
|
+
const locale = (value, field) => {
|
|
23
|
+
let htmlLocale;
|
|
24
|
+
try {
|
|
25
|
+
htmlLocale = new Intl.Locale(value.lang);
|
|
26
|
+
} catch {
|
|
27
|
+
fail(field, "Invalid BCP 47 HTML language");
|
|
28
|
+
}
|
|
29
|
+
if (!["ltr", "rtl"].includes(value.dir)) fail(field, "Invalid HTML direction");
|
|
30
|
+
const match = /^([a-z]{2,3})[_-]([a-z]{2})(?:_[a-z0-9]+)*$/iu.exec(value.locale);
|
|
31
|
+
if (!match) {
|
|
32
|
+
fail(field, "Explicit regional locale required; no territory is inferred");
|
|
33
|
+
return "";
|
|
34
|
+
}
|
|
35
|
+
if (htmlLocale && (htmlLocale.language.toLowerCase() !== match[1].toLowerCase() || htmlLocale.region && htmlLocale.region.toUpperCase() !== match[2].toUpperCase())) fail(field, "HTML and OG locales disagree");
|
|
36
|
+
return `${match[1].toLowerCase()}_${match[2].toUpperCase()}`;
|
|
37
|
+
};
|
|
38
|
+
const ogLocale = locale(language, "language");
|
|
39
|
+
const head = [];
|
|
40
|
+
const meta = (name, content, property = true) => head.push({
|
|
41
|
+
tag: "meta",
|
|
42
|
+
attrs: {
|
|
43
|
+
[property ? "property" : "name"]: name,
|
|
44
|
+
content
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
const applicable = input.state === "public" && input.status === 200;
|
|
48
|
+
meta("robots", applicable && !input.noindex ? "index, follow" : "noindex, nofollow", false);
|
|
49
|
+
const result = {
|
|
50
|
+
baseline: metadataBaseline,
|
|
51
|
+
ready: false,
|
|
52
|
+
documentReady: false,
|
|
53
|
+
graphReady: false,
|
|
54
|
+
applicable,
|
|
55
|
+
htmlAttributes: {
|
|
56
|
+
lang: language.lang,
|
|
57
|
+
dir: language.dir
|
|
58
|
+
},
|
|
59
|
+
head,
|
|
60
|
+
diagnostics
|
|
61
|
+
};
|
|
62
|
+
if (!applicable) {
|
|
63
|
+
if (input.statusTitle !== void 0) {
|
|
64
|
+
const title = text(input.statusTitle, "statusTitle");
|
|
65
|
+
if (!diagnostics.some((d) => d.field === "statusTitle")) head.push({
|
|
66
|
+
tag: "title",
|
|
67
|
+
text: title
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
result.ready = diagnostics.length === 0;
|
|
71
|
+
result.documentReady = result.ready && head.some((node) => node.tag === "title");
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
let origin = "";
|
|
75
|
+
try {
|
|
76
|
+
const parsed = new URL(input.site.origin);
|
|
77
|
+
if (parsed.href !== `${parsed.origin}/` || parsed.protocol !== "https:" || parsed.username || parsed.password || /^(localhost|.*\.localhost|.*\.local|.*\.invalid|127\..*|0\.0\.0\.0|\[::1\])$/iu.test(parsed.hostname)) throw new Error();
|
|
78
|
+
origin = parsed.origin;
|
|
79
|
+
} catch {
|
|
80
|
+
fail("site.origin", "Expected reviewed public HTTPS origin");
|
|
81
|
+
}
|
|
82
|
+
const url = (value, field, identity) => {
|
|
83
|
+
try {
|
|
84
|
+
const parsed = new URL(value ?? "");
|
|
85
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.hash || /^(localhost|.*\.localhost|.*\.local|.*\.invalid|127\..*|0\.0\.0\.0|\[::1\])$/iu.test(parsed.hostname)) throw new Error();
|
|
86
|
+
if (identity && parsed.origin !== origin) throw new Error();
|
|
87
|
+
for (const key of parsed.searchParams.keys()) if (/^(utm_|fbclid$|gclid$|token$|sig$|signature$|expires$|x-amz-|x-goog-|awsaccesskeyid$|preview)/iu.test(key) || identity && !input.site.identityQueryKeys?.includes(key)) throw new Error();
|
|
88
|
+
return parsed.href;
|
|
89
|
+
} catch {
|
|
90
|
+
fail(field, "Invalid public HTTPS URL or unreviewed canonical identity/query");
|
|
91
|
+
return "";
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const selected = (key) => input.overrides?.[key] ?? input.page?.[key] ?? input.site[key];
|
|
95
|
+
const title = text(selected("title"), "title");
|
|
96
|
+
const shareTitle = text(input.overrides?.shareTitle ?? title, "shareTitle");
|
|
97
|
+
const description = text(selected("description"), "description");
|
|
98
|
+
const name = text(input.site.name, "site.name");
|
|
99
|
+
const canonical = url(input.canonical, "canonical", true);
|
|
100
|
+
head.push({
|
|
101
|
+
tag: "title",
|
|
102
|
+
text: title
|
|
103
|
+
}, {
|
|
104
|
+
tag: "link",
|
|
105
|
+
attrs: {
|
|
106
|
+
rel: "canonical",
|
|
107
|
+
href: canonical
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
meta("description", description, false);
|
|
111
|
+
for (const [key, value] of Object.entries({
|
|
112
|
+
title: shareTitle,
|
|
113
|
+
type: input.kind === "article" ? "article" : "website",
|
|
114
|
+
url: canonical,
|
|
115
|
+
description,
|
|
116
|
+
site_name: name,
|
|
117
|
+
locale: ogLocale
|
|
118
|
+
})) meta(`og:${key}`, value);
|
|
119
|
+
const images = selected("images");
|
|
120
|
+
if (!images?.length) fail("images", "At least one reviewed share image required");
|
|
121
|
+
images?.forEach((image, index) => {
|
|
122
|
+
const field = `images.${index}`;
|
|
123
|
+
const src = url(image.url, `${field}.url`, false);
|
|
124
|
+
const alt = text(image.alt, `${field}.alt`);
|
|
125
|
+
if (![image.width, image.height].every((n) => Number.isSafeInteger(n) && n > 0)) fail(field, "Known positive integer dimensions required");
|
|
126
|
+
if (image.type && !["image/jpeg", "image/png"].includes(image.type)) fail(field, "OG-1 supports verified JPEG/PNG MIME only");
|
|
127
|
+
meta("og:image", src);
|
|
128
|
+
meta("og:image:alt", alt);
|
|
129
|
+
meta("og:image:width", String(image.width));
|
|
130
|
+
meta("og:image:height", String(image.height));
|
|
131
|
+
if (image.type) meta("og:image:type", image.type);
|
|
132
|
+
});
|
|
133
|
+
const hasPublishedTranslations = input.translations?.some((t) => t.published) ?? false;
|
|
134
|
+
const languageKey = (value) => {
|
|
135
|
+
try {
|
|
136
|
+
return new Intl.Locale(value.lang).baseName;
|
|
137
|
+
} catch {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
if (hasPublishedTranslations && input.language) locale(input.site.language, "site.language");
|
|
142
|
+
const defaultLanguage = languageKey(input.site.language);
|
|
143
|
+
let defaultCanonical = defaultLanguage && languageKey(language) === defaultLanguage ? canonical : void 0;
|
|
144
|
+
const locales = /* @__PURE__ */ new Set([ogLocale]);
|
|
145
|
+
const langs = /* @__PURE__ */ new Set([language.lang.toLowerCase()]);
|
|
146
|
+
if (hasPublishedTranslations) head.push({
|
|
147
|
+
tag: "link",
|
|
148
|
+
attrs: {
|
|
149
|
+
rel: "alternate",
|
|
150
|
+
hreflang: language.lang,
|
|
151
|
+
href: canonical
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
input.translations?.filter((t) => t.published).forEach((translation, index) => {
|
|
155
|
+
const field = `translations.${index}`;
|
|
156
|
+
const translatedLocale = locale(translation, field);
|
|
157
|
+
const href = url(translation.canonical, field, true);
|
|
158
|
+
if (langs.has(translation.lang.toLowerCase())) {
|
|
159
|
+
fail(field, "Duplicate/current translation language");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
langs.add(translation.lang.toLowerCase());
|
|
163
|
+
if (defaultLanguage && languageKey(translation) === defaultLanguage) defaultCanonical = href;
|
|
164
|
+
head.push({
|
|
165
|
+
tag: "link",
|
|
166
|
+
attrs: {
|
|
167
|
+
rel: "alternate",
|
|
168
|
+
hreflang: translation.lang,
|
|
169
|
+
href
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
if (!locales.has(translatedLocale)) {
|
|
173
|
+
meta("og:locale:alternate", translatedLocale);
|
|
174
|
+
locales.add(translatedLocale);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (hasPublishedTranslations && defaultCanonical) head.push({
|
|
178
|
+
tag: "link",
|
|
179
|
+
attrs: {
|
|
180
|
+
rel: "alternate",
|
|
181
|
+
hreflang: "x-default",
|
|
182
|
+
href: defaultCanonical
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
if (input.kind === "article" && input.article) {
|
|
186
|
+
for (const [key, value] of [["published_time", input.article.publishedTime], ["modified_time", input.article.modifiedTime]]) {
|
|
187
|
+
if (value === void 0) continue;
|
|
188
|
+
if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d(?::\d\d(?:\.\d+)?)?(?:Z|[+-]\d\d:\d\d)$/u.test(value) || !Number.isFinite(Date.parse(value)) || Number(value.slice(8, 10)) > new Date(Date.UTC(Number(value.slice(0, 4)), Number(value.slice(5, 7)), 0)).getUTCDate()) fail(`article.${key}`, "Timestamp must include a timezone");
|
|
189
|
+
else meta(`article:${key}`, value);
|
|
190
|
+
}
|
|
191
|
+
input.article.authors?.forEach((author) => meta("article:author", url(author, "article.authors", false)));
|
|
192
|
+
}
|
|
193
|
+
result.ready = diagnostics.length === 0;
|
|
194
|
+
result.graphReady = result.ready;
|
|
195
|
+
result.documentReady = !diagnostics.some((d) => [
|
|
196
|
+
"title",
|
|
197
|
+
"description",
|
|
198
|
+
"canonical",
|
|
199
|
+
"site.origin"
|
|
200
|
+
].includes(d.field) || d.field === "language" && /HTML|BCP/u.test(d.message));
|
|
201
|
+
if (!result.ready) {
|
|
202
|
+
const invalid = (field) => diagnostics.some((d) => d.field === field);
|
|
203
|
+
result.head = head.filter((node) => {
|
|
204
|
+
if (node.tag === "title") return !invalid("title");
|
|
205
|
+
if (node.tag === "meta") return node.attrs.name === "robots" || node.attrs.name === "description" && !invalid("description");
|
|
206
|
+
if (node.attrs.rel === "canonical") return !invalid("canonical");
|
|
207
|
+
return node.attrs.rel === "alternate" && !invalid("canonical") && !diagnostics.some((d) => d.field.startsWith("translations."));
|
|
208
|
+
});
|
|
209
|
+
} else result.htmlAttributes.prefix = "og: https://ogp.me/ns#";
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
const escape = (value) => value.replaceAll("&", "&").replaceAll("\"", """).replaceAll("'", "'").replaceAll("<", "<").replaceAll(">", ">");
|
|
213
|
+
/** Safe HTML fragment for Astro set:html or other server serialization; preserves repeated image groups. */
|
|
214
|
+
function renderMetadataHead(metadata) {
|
|
215
|
+
return metadata.head.map((node) => node.tag === "title" ? `<title>${escape(node.text)}</title>` : `<${node.tag} ${Object.entries(node.attrs).map(([key, value]) => `${key}="${escape(value)}"`).join(" ")}>`).join("\n");
|
|
216
|
+
}
|
|
217
|
+
//#endregion
|
|
218
|
+
export { metadataBaseline, normalizeMetadataText, renderMetadataHead, resolveMetadata };
|
package/dist/server.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { type ContentBindings, ContentConfig, ContentConsentConfig, ContentRuntime, type WordPressClient, createContentRuntime };
|
|
1
|
+
import { T as ContentBindings, a as ContentExtension, c as createContentRuntime, i as ContentConsentConfig, n as CollectionConfig, o as ContentRuntime, r as ContentConfig, s as ExtensionNode, t as WordPressClient } from "./wordpress-D_HG2rEt.js";
|
|
2
|
+
export { CollectionConfig, type ContentBindings, ContentConfig, ContentConsentConfig, ContentExtension, ContentRuntime, ExtensionNode, type WordPressClient, createContentRuntime };
|