@contentrain/emitter-astro 0.17.0 → 0.18.1
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 +34 -1
- package/dist/index.d.mts +91 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +223 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -722,9 +722,7 @@ function scaffoldFiles(ir, options, noindex = [], redirectsConfig = null, runtim
|
|
|
722
722
|
"@tailwindcss/vite": "^4.0.0"
|
|
723
723
|
} : {},
|
|
724
724
|
...sitemap ? { "@astrojs/sitemap": "^3.7.0" } : {},
|
|
725
|
-
...images ? { sharp: "^0.34.0" } : {}
|
|
726
|
-
},
|
|
727
|
-
devDependencies: {
|
|
725
|
+
...images ? { sharp: "^0.34.0" } : {},
|
|
728
726
|
"@astrojs/check": "^0.9.0",
|
|
729
727
|
typescript: "^5.7.0"
|
|
730
728
|
}
|
|
@@ -1733,7 +1731,9 @@ function scanTags(html) {
|
|
|
1733
1731
|
});
|
|
1734
1732
|
i = j + 1;
|
|
1735
1733
|
if (!closing && RAW_TEXT_TAGS.has(name)) {
|
|
1736
|
-
const
|
|
1734
|
+
const end = new RegExp(`</${name}`, "gi");
|
|
1735
|
+
end.lastIndex = i;
|
|
1736
|
+
const close = end.exec(html)?.index ?? -1;
|
|
1737
1737
|
i = close === -1 ? html.length : close;
|
|
1738
1738
|
}
|
|
1739
1739
|
}
|
|
@@ -3228,7 +3228,8 @@ export function runtimeOf(host: HTMLElement): Runtime {
|
|
|
3228
3228
|
}
|
|
3229
3229
|
|
|
3230
3230
|
function entryOf(host: HTMLElement): EntryRef {
|
|
3231
|
-
|
|
3231
|
+
const locale = host.dataset.locale
|
|
3232
|
+
return { model_id: host.dataset.model ?? '', entry_id: host.dataset.entry ?? '', ...(locale ? { locale } : {}) }
|
|
3232
3233
|
}
|
|
3233
3234
|
|
|
3234
3235
|
function setStatus(form: HTMLFormElement, html: string): void {
|
|
@@ -3766,7 +3767,7 @@ const REDIRECT_STATUSES = [
|
|
|
3766
3767
|
/** Whitespace, a control character or DEL anywhere in the string. */
|
|
3767
3768
|
const hasControlOrSpace = (value) => [...value].some((char) => char.charCodeAt(0) <= 32 || char.charCodeAt(0) === 127);
|
|
3768
3769
|
/** `/old/` and `/old` are one address in the directory build format. */
|
|
3769
|
-
function addressKey$
|
|
3770
|
+
function addressKey$2(path) {
|
|
3770
3771
|
return path === "/" ? "/" : `${path.replace(/\/+$/, "")}/`;
|
|
3771
3772
|
}
|
|
3772
3773
|
/**
|
|
@@ -3777,7 +3778,7 @@ function addressKey$1(path) {
|
|
|
3777
3778
|
function builtAddresses(routes, content) {
|
|
3778
3779
|
const out = /* @__PURE__ */ new Map();
|
|
3779
3780
|
const put = (path, routeId) => {
|
|
3780
|
-
if (path !== null && !out.has(addressKey$
|
|
3781
|
+
if (path !== null && !out.has(addressKey$2(path))) out.set(addressKey$2(path), routeId);
|
|
3781
3782
|
};
|
|
3782
3783
|
for (const route of routes) {
|
|
3783
3784
|
const pagePath = patternToPagePath(route.pattern);
|
|
@@ -3879,7 +3880,7 @@ function planRedirects(redirects, built, options = {}) {
|
|
|
3879
3880
|
skip("to is not a site-root path or an http(s) URL");
|
|
3880
3881
|
continue;
|
|
3881
3882
|
}
|
|
3882
|
-
const key = addressKey$
|
|
3883
|
+
const key = addressKey$2(from.path);
|
|
3883
3884
|
const owner = built.get(key);
|
|
3884
3885
|
if (owner !== void 0) {
|
|
3885
3886
|
skip(`the migrated site builds a page at ${key} (route ${owner}) — the page is kept`);
|
|
@@ -3901,7 +3902,7 @@ function planRedirects(redirects, built, options = {}) {
|
|
|
3901
3902
|
} catch {
|
|
3902
3903
|
target = redirect.to;
|
|
3903
3904
|
}
|
|
3904
|
-
if (addressKey$
|
|
3905
|
+
if (addressKey$2(target) === key) {
|
|
3905
3906
|
skip("to is the same address as from");
|
|
3906
3907
|
continue;
|
|
3907
3908
|
}
|
|
@@ -4006,6 +4007,172 @@ function hostRedirectFiles(config, host) {
|
|
|
4006
4007
|
};
|
|
4007
4008
|
}
|
|
4008
4009
|
//#endregion
|
|
4010
|
+
//#region src/published.ts
|
|
4011
|
+
/**
|
|
4012
|
+
* Why an entry is not built, or null when it is. `now` is epoch ms. With
|
|
4013
|
+
* `requireStatus` an entry that carries no status is held back: the producer
|
|
4014
|
+
* said it passes every entry's status, so a missing one is a gap, not a
|
|
4015
|
+
* published entry.
|
|
4016
|
+
*/
|
|
4017
|
+
function holdReason(post, now, requireStatus = false) {
|
|
4018
|
+
if (post.visibility !== void 0 && post.visibility !== "public") return "visibility";
|
|
4019
|
+
if (post.status === void 0 && requireStatus) return "status_missing";
|
|
4020
|
+
if (post.status !== void 0 && post.status !== "published") return "status";
|
|
4021
|
+
if (post.publish_at === void 0) return null;
|
|
4022
|
+
const at = Date.parse(post.publish_at);
|
|
4023
|
+
if (Number.isNaN(at)) return "publish_at_invalid";
|
|
4024
|
+
return at > now ? "scheduled" : null;
|
|
4025
|
+
}
|
|
4026
|
+
const bareHost = (host) => host.replace(/^www\./, "").toLowerCase();
|
|
4027
|
+
/** `/a/b` and `/a/b/` are one address. */
|
|
4028
|
+
const addressKey$1 = (path) => path === "/" ? "/" : `${path.replace(/\/+$/, "")}/`;
|
|
4029
|
+
/**
|
|
4030
|
+
* The held-back address an href points at, or null: a site-root path, or an
|
|
4031
|
+
* absolute URL on the site's own host (`www.` either way).
|
|
4032
|
+
*/
|
|
4033
|
+
function withheldTarget(href, gone, siteUrl) {
|
|
4034
|
+
if (!gone.size) return null;
|
|
4035
|
+
let site;
|
|
4036
|
+
try {
|
|
4037
|
+
site = siteUrl ? new URL(siteUrl) : void 0;
|
|
4038
|
+
} catch {
|
|
4039
|
+
site = void 0;
|
|
4040
|
+
}
|
|
4041
|
+
let url;
|
|
4042
|
+
try {
|
|
4043
|
+
url = new URL(href, site ?? "http://site.invalid");
|
|
4044
|
+
} catch {
|
|
4045
|
+
return null;
|
|
4046
|
+
}
|
|
4047
|
+
if (!(site ? bareHost(url.host) === bareHost(site.host) : url.host === "site.invalid") || !/^https?:$/.test(url.protocol)) return null;
|
|
4048
|
+
let path = url.pathname;
|
|
4049
|
+
try {
|
|
4050
|
+
path = decodeURI(path);
|
|
4051
|
+
} catch {}
|
|
4052
|
+
return gone.has(addressKey$1(path)) ? path : null;
|
|
4053
|
+
}
|
|
4054
|
+
/**
|
|
4055
|
+
* The content with every unpublished entry removed — from its collection and
|
|
4056
|
+
* from the list pages that carry it as an item — and links to them unwrapped.
|
|
4057
|
+
* Content with nothing held back is returned as given.
|
|
4058
|
+
*/
|
|
4059
|
+
function publishedContent(routes, content, now, siteUrl, requireStatus = false) {
|
|
4060
|
+
const withheld = [];
|
|
4061
|
+
const keep = (collection) => (post) => {
|
|
4062
|
+
const reason = holdReason(post, now, requireStatus);
|
|
4063
|
+
if (!reason) return true;
|
|
4064
|
+
withheld.push({
|
|
4065
|
+
collection,
|
|
4066
|
+
slug: post.slug,
|
|
4067
|
+
...post.status ? { status: post.status } : {},
|
|
4068
|
+
...post.visibility ? { visibility: post.visibility } : {},
|
|
4069
|
+
...post.publish_at ? { publish_at: post.publish_at } : {},
|
|
4070
|
+
reason
|
|
4071
|
+
});
|
|
4072
|
+
return false;
|
|
4073
|
+
};
|
|
4074
|
+
const collections = content.collections ? Object.fromEntries(Object.entries(content.collections).map(([name, posts]) => [name, posts.filter(keep(name))])) : void 0;
|
|
4075
|
+
const isLive = (post) => holdReason(post, now, requireStatus) === null;
|
|
4076
|
+
const posts = content.posts?.filter(content.collections?.["posts"] ? isLive : keep(DEFAULT_COLLECTION));
|
|
4077
|
+
const queries = content.queries ? Object.fromEntries(Object.entries(content.queries).map(([id, pages]) => [id, pages.map((page) => page.items.every(isLive) ? page : {
|
|
4078
|
+
...page,
|
|
4079
|
+
items: page.items.filter(isLive)
|
|
4080
|
+
})])) : void 0;
|
|
4081
|
+
const listedOnly = Object.values(content.queries ?? {}).flat().flatMap((p) => p.items).some((item) => !isLive(item));
|
|
4082
|
+
if (!withheld.length && !listedOnly) return {
|
|
4083
|
+
content,
|
|
4084
|
+
withheld,
|
|
4085
|
+
links: [],
|
|
4086
|
+
gone: /* @__PURE__ */ new Set()
|
|
4087
|
+
};
|
|
4088
|
+
const filtered = {
|
|
4089
|
+
...content,
|
|
4090
|
+
...posts ? { posts } : {},
|
|
4091
|
+
...collections ? { collections } : {},
|
|
4092
|
+
...queries ? { queries } : {}
|
|
4093
|
+
};
|
|
4094
|
+
const built = builtAddresses(routes, filtered);
|
|
4095
|
+
const gone = /* @__PURE__ */ new Set();
|
|
4096
|
+
for (const route of routes) {
|
|
4097
|
+
const pagePath = patternToPagePath(route.pattern);
|
|
4098
|
+
if (!pagePath || !pagePath.includes("[") || route.kind !== "single" && route.collection === void 0) continue;
|
|
4099
|
+
const collection = route.collection ?? "posts";
|
|
4100
|
+
for (const post of collectionItems(content, collection)) {
|
|
4101
|
+
const path = entryPath(route.pattern, {
|
|
4102
|
+
...post.params,
|
|
4103
|
+
slug: post.slug
|
|
4104
|
+
});
|
|
4105
|
+
if (path && !isLive(post) && !built.has(addressKey$1(path))) gone.add(addressKey$1(path));
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
const links = [];
|
|
4109
|
+
const unlink = (html, page) => {
|
|
4110
|
+
if (!html || !gone.size) return html;
|
|
4111
|
+
return html.replace(ANCHOR_RE, (anchor, double, single, inner) => {
|
|
4112
|
+
const to = withheldTarget((double ?? single ?? "").replace(/&/g, "&"), gone, siteUrl);
|
|
4113
|
+
if (!to) return anchor;
|
|
4114
|
+
links.push({
|
|
4115
|
+
page,
|
|
4116
|
+
href: to
|
|
4117
|
+
});
|
|
4118
|
+
return inner;
|
|
4119
|
+
});
|
|
4120
|
+
};
|
|
4121
|
+
const unlinkPost = (post, page) => {
|
|
4122
|
+
const body = unlink(post.body, page) ?? post.body;
|
|
4123
|
+
const excerpt_html = unlink(post.excerpt_html, page);
|
|
4124
|
+
return body === post.body && excerpt_html === post.excerpt_html ? post : {
|
|
4125
|
+
...post,
|
|
4126
|
+
body,
|
|
4127
|
+
...excerpt_html !== void 0 ? { excerpt_html } : {}
|
|
4128
|
+
};
|
|
4129
|
+
};
|
|
4130
|
+
const pageOf = (collection, post) => {
|
|
4131
|
+
const route = routes.find((r) => (r.kind === "single" || r.collection !== void 0) && (r.collection ?? "posts") === collection);
|
|
4132
|
+
return (route && entryPath(route.pattern, {
|
|
4133
|
+
...post.params,
|
|
4134
|
+
slug: post.slug
|
|
4135
|
+
})) ?? `${collection}/${post.slug}`;
|
|
4136
|
+
};
|
|
4137
|
+
if (filtered.collections) filtered.collections = Object.fromEntries(Object.entries(filtered.collections).map(([name, list]) => [name, list.map((p) => unlinkPost(p, pageOf(name, p)))]));
|
|
4138
|
+
if (filtered.posts) filtered.posts = filtered.posts.map((p) => unlinkPost(p, pageOf(DEFAULT_COLLECTION, p)));
|
|
4139
|
+
if (filtered.queries) filtered.queries = Object.fromEntries(Object.entries(filtered.queries).map(([id, pages]) => [id, pages.map((page) => {
|
|
4140
|
+
const route = routes.find((r) => r.query === id);
|
|
4141
|
+
const at = (route && entryPath(route.pattern, page.params)) ?? `query ${id}`;
|
|
4142
|
+
return {
|
|
4143
|
+
...page,
|
|
4144
|
+
items: page.items.map((item) => unlinkPost(item, at))
|
|
4145
|
+
};
|
|
4146
|
+
})]));
|
|
4147
|
+
return {
|
|
4148
|
+
content: filtered,
|
|
4149
|
+
withheld,
|
|
4150
|
+
links,
|
|
4151
|
+
gone
|
|
4152
|
+
};
|
|
4153
|
+
}
|
|
4154
|
+
const ANCHOR_RE = /<a\b[^>]*?\shref\s*=\s*(?:"([^"]*)"|'([^']*)')[^>]*>([\s\S]*?)<\/a\s*>/gi;
|
|
4155
|
+
/** The report lines for what was held back. */
|
|
4156
|
+
function withheldWarnings(withheld, links) {
|
|
4157
|
+
const out = [];
|
|
4158
|
+
if (withheld.length) {
|
|
4159
|
+
const count = (reason) => withheld.filter((w) => w.reason === reason).length;
|
|
4160
|
+
const parts = [
|
|
4161
|
+
count("visibility") && `${count("visibility")} password-protected or private`,
|
|
4162
|
+
count("status_missing") && `${count("status_missing")} with no status (options.requireStatus)`,
|
|
4163
|
+
count("status") && `${count("status")} not published (draft, in review, rejected or archived)`,
|
|
4164
|
+
count("scheduled") && `${count("scheduled")} scheduled for later — built by the first emit after their publish_at`,
|
|
4165
|
+
count("publish_at_invalid") && `${count("publish_at_invalid")} with a publish_at that is not a date`
|
|
4166
|
+
].filter(Boolean);
|
|
4167
|
+
out.push(`unpublished: ${withheld.length} entries not built — ${parts.join("; ")}. They stay in the content store; EmitResult.withheld lists each`);
|
|
4168
|
+
}
|
|
4169
|
+
if (links.length) {
|
|
4170
|
+
const sample = links.slice(0, 5).map((l) => `${l.href} (on ${l.page})`).join(", ");
|
|
4171
|
+
out.push(`unpublished: ${links.length} links from published pages to unpublished entries kept as plain text — ${sample}${links.length > 5 ? " …" : ""}`);
|
|
4172
|
+
}
|
|
4173
|
+
return out;
|
|
4174
|
+
}
|
|
4175
|
+
//#endregion
|
|
4009
4176
|
//#region src/seo-entry.ts
|
|
4010
4177
|
/**
|
|
4011
4178
|
* A plugin's unrendered template, in its own syntax: Yoast `%%title%%`,
|
|
@@ -4184,6 +4351,27 @@ function emitAstroProject(input) {
|
|
|
4184
4351
|
files[path] = content;
|
|
4185
4352
|
}
|
|
4186
4353
|
};
|
|
4354
|
+
let now = Date.now();
|
|
4355
|
+
if (input.options?.now !== void 0) {
|
|
4356
|
+
const at = Date.parse(input.options.now);
|
|
4357
|
+
if (Number.isNaN(at)) warnings.push(`options.now "${input.options.now}" is not a date — publish_at is compared with the time of this emit`);
|
|
4358
|
+
else now = at;
|
|
4359
|
+
}
|
|
4360
|
+
const published = publishedContent(ir.routes, input.content ?? {}, now, ir.site.url, input.options?.requireStatus === true);
|
|
4361
|
+
const content = published.content;
|
|
4362
|
+
warnings.push(...withheldWarnings(published.withheld, published.links));
|
|
4363
|
+
const withheldManual = [];
|
|
4364
|
+
const toPublished = (rules) => rules?.filter((redirect) => {
|
|
4365
|
+
const target = withheldTarget(redirect.to, published.gone, ir.site.url);
|
|
4366
|
+
if (target) withheldManual.push({
|
|
4367
|
+
redirect,
|
|
4368
|
+
reason: `to ${target}, an entry that is not published — the build has no page there`
|
|
4369
|
+
});
|
|
4370
|
+
return !target;
|
|
4371
|
+
});
|
|
4372
|
+
const siteRedirects = toPublished(input.redirects);
|
|
4373
|
+
const hostOnlyRedirects = toPublished(input.hostRedirects);
|
|
4374
|
+
if (withheldManual.length) warnings.push(`unpublished: ${withheldManual.length} redirects point at unpublished entries — not written (EmitResult.redirects.manual has each)`);
|
|
4187
4375
|
const siteLang = ir.site.locales?.[0] ?? "en";
|
|
4188
4376
|
const linkable = ir.site.url ? linkSources(ir.routes, siteLang) : [];
|
|
4189
4377
|
const feedSource = input.options?.feed !== false ? linkable.find((s) => s.collection === DEFAULT_COLLECTION) : void 0;
|
|
@@ -4191,11 +4379,11 @@ function emitAstroProject(input) {
|
|
|
4191
4379
|
const archiveFeeds = feedSource ? archiveFeedSources(ir.routes, siteLang) : [];
|
|
4192
4380
|
const archiveFeedRoutes = new Set(archiveFeeds.map((a) => a.routeId));
|
|
4193
4381
|
if (!ir.site.url && (input.options?.feed !== false || input.options?.llms !== false)) warnings.push("site.url is empty — no RSS feed and no llms.txt are built; both name pages by absolute address");
|
|
4194
|
-
const redirectPlan =
|
|
4195
|
-
if (redirectPlan?.manual.length) warnings.push(`redirects: ${redirectPlan.manual.length} of ${
|
|
4382
|
+
const redirectPlan = siteRedirects ? planRedirects(siteRedirects, builtAddresses(ir.routes, content)) : void 0;
|
|
4383
|
+
if (redirectPlan?.manual.length) warnings.push(`redirects: ${redirectPlan.manual.length} of ${siteRedirects.length} rules not written to astro.config — set them up at the host (EmitResult.redirects.manual has each with its reason)`);
|
|
4196
4384
|
const seo = input.options?.seo !== false;
|
|
4197
|
-
const noindex = noindexPaths(ir.routes,
|
|
4198
|
-
const built = builtAddresses(ir.routes,
|
|
4385
|
+
const noindex = noindexPaths(ir.routes, content);
|
|
4386
|
+
const built = builtAddresses(ir.routes, content);
|
|
4199
4387
|
const sourceConfig = redirectPlan?.config ?? {};
|
|
4200
4388
|
const claimed = new Set(Object.keys(sourceConfig).map((from) => from.replace(/\/+$/, "").toLowerCase()));
|
|
4201
4389
|
const feedRules = [];
|
|
@@ -4210,7 +4398,7 @@ function emitAstroProject(input) {
|
|
|
4210
4398
|
});
|
|
4211
4399
|
};
|
|
4212
4400
|
if (feedSource) addFeedRule(FEED_REDIRECT_FROM, FEED_PATH);
|
|
4213
|
-
for (const source of archiveFeeds) for (const page of
|
|
4401
|
+
for (const source of archiveFeeds) for (const page of content.queries?.[source.query] ?? []) {
|
|
4214
4402
|
const address = entryPath(source.pattern, page.params);
|
|
4215
4403
|
if (address) addFeedRule(`${address}feed/`, `${address}feed.xml`);
|
|
4216
4404
|
}
|
|
@@ -4222,7 +4410,7 @@ function emitAstroProject(input) {
|
|
|
4222
4410
|
};
|
|
4223
4411
|
const hasRedirects = Object.keys(redirectConfig).length > 0;
|
|
4224
4412
|
add(scaffoldFiles(ir, input.options ?? {}, noindex, hasRedirects ? astroRedirectsConfig(sortedConfig(redirectConfig)) : null, input.runtime));
|
|
4225
|
-
const hostOnlyPlan =
|
|
4413
|
+
const hostOnlyPlan = hostOnlyRedirects ? planRedirects(hostOnlyRedirects, built, { hostOnly: true }) : void 0;
|
|
4226
4414
|
const hostOnlyConfig = {};
|
|
4227
4415
|
const hostOnlyRule = /* @__PURE__ */ new Map();
|
|
4228
4416
|
const hostOnlyManual = [...hostOnlyPlan?.manual ?? []];
|
|
@@ -4264,7 +4452,7 @@ function emitAstroProject(input) {
|
|
|
4264
4452
|
});
|
|
4265
4453
|
else if (hostOnlyOver.length) warnings.push(`hostRedirects: ${hostOnlyOver.length} host-only rules not written to vercel.json at its rule limit (${HOST_RULE_LIMIT / 2} rules) — public/_redirects (Netlify) has them; on Vercel they are not served. Name the host (options.redirectHost) to have them reported as manual`);
|
|
4266
4454
|
const unservedHostOnly = new Set(hostOnlyManual.map((m) => m.redirect));
|
|
4267
|
-
if (hostOnlyManual.length) warnings.push(`hostRedirects: ${hostOnlyManual.length} of ${
|
|
4455
|
+
if (hostOnlyManual.length) warnings.push(`hostRedirects: ${hostOnlyManual.length} of ${hostOnlyRedirects.length} host-only rules not written — set them up at the host (EmitResult.redirects.manual has each with its reason)`);
|
|
4268
4456
|
if (hostRedirects) {
|
|
4269
4457
|
add(hostRedirects.files);
|
|
4270
4458
|
const overLimit = hostRedirects.over_limit.filter((from) => !hostOnly(from));
|
|
@@ -4300,7 +4488,7 @@ function emitAstroProject(input) {
|
|
|
4300
4488
|
const bodyMarkersByFamily = /* @__PURE__ */ new Map();
|
|
4301
4489
|
for (const route of ir.routes) {
|
|
4302
4490
|
if (route.kind !== "single" && route.collection === void 0) continue;
|
|
4303
|
-
const posts = collectionItems(
|
|
4491
|
+
const posts = collectionItems(content, route.collection ?? "posts");
|
|
4304
4492
|
const ids = bodyMarkersByFamily.get(route.family) ?? /* @__PURE__ */ new Set();
|
|
4305
4493
|
for (const post of posts) for (const id of componentMarkers(post.body)) ids.add(id);
|
|
4306
4494
|
if (ids.size) bodyMarkersByFamily.set(route.family, ids);
|
|
@@ -4309,7 +4497,7 @@ function emitAstroProject(input) {
|
|
|
4309
4497
|
const queryBound = /* @__PURE__ */ new Set();
|
|
4310
4498
|
for (const family of ir.families) for (const placement of family.components ?? []) {
|
|
4311
4499
|
if (!placement.query) continue;
|
|
4312
|
-
const pages =
|
|
4500
|
+
const pages = content.queries?.[placement.query];
|
|
4313
4501
|
if (!pages) {
|
|
4314
4502
|
warnings.push(`family ${family.id}: placement "${placement.component}" binds query "${placement.query}", which is not in input.content.queries — binding dropped, region left as cloned markup`);
|
|
4315
4503
|
continue;
|
|
@@ -4342,23 +4530,23 @@ function emitAstroProject(input) {
|
|
|
4342
4530
|
for (const ref of refs ?? []) if (!providedCss.has(ref.split("/").pop() ?? ref)) warnings.push(`${owner}: css file "${ref}" not provided in input.css`);
|
|
4343
4531
|
};
|
|
4344
4532
|
for (const family of ir.families) missingCss(family.css.files, `family ${family.id}`);
|
|
4345
|
-
const allPosts = [...
|
|
4533
|
+
const allPosts = [...content.posts ?? [], ...Object.values(content.collections ?? {}).flat()];
|
|
4346
4534
|
for (const post of allPosts) missingCss(post.css, `post ${post.slug}`);
|
|
4347
|
-
for (const [queryId, queryPages] of Object.entries(
|
|
4348
|
-
const trailOwners = [...Object.entries(
|
|
4535
|
+
for (const [queryId, queryPages] of Object.entries(content.queries ?? {})) for (const qp of queryPages) missingCss(qp.css, `query ${queryId}`);
|
|
4536
|
+
const trailOwners = [...Object.entries(content.collections ?? {}), ...content.posts ? [[DEFAULT_COLLECTION, content.posts]] : []];
|
|
4349
4537
|
for (const [name, posts] of trailOwners) {
|
|
4350
4538
|
const bad = (posts ?? []).filter((p) => badTrail(p.breadcrumbs)).length;
|
|
4351
4539
|
if (bad) warnings.push(`collection ${name}: ${bad} breadcrumb trails dropped — every crumb needs a name and a site-root-relative path`);
|
|
4352
4540
|
const badLd = (posts ?? []).filter((p) => badSchema(p.schema)).length;
|
|
4353
4541
|
if (badLd) warnings.push(`collection ${name}: ${badLd} schema values are not JSON-LD objects — those pages print the generated structured data instead`);
|
|
4354
4542
|
}
|
|
4355
|
-
for (const [queryId, queryPages] of Object.entries(
|
|
4543
|
+
for (const [queryId, queryPages] of Object.entries(content.queries ?? {})) {
|
|
4356
4544
|
const bad = queryPages.filter((qp) => badTrail(qp.breadcrumbs)).length;
|
|
4357
4545
|
if (bad) warnings.push(`query ${queryId}: ${bad} breadcrumb trails dropped — every crumb needs a name and a site-root-relative path`);
|
|
4358
4546
|
const badLd = queryPages.filter((qp) => badSchema(qp.schema)).length;
|
|
4359
4547
|
if (badLd) warnings.push(`query ${queryId}: ${badLd} schema values are not JSON-LD objects — those pages print the generated structured data instead`);
|
|
4360
4548
|
}
|
|
4361
|
-
let routeContent =
|
|
4549
|
+
let routeContent = content;
|
|
4362
4550
|
if (seo) {
|
|
4363
4551
|
const alternates = withAlternates(ir.routes, routeContent, lang);
|
|
4364
4552
|
routeContent = alternates.content;
|
|
@@ -4413,8 +4601,8 @@ function emitAstroProject(input) {
|
|
|
4413
4601
|
if (uncovered.length) warnings.push(`ui-strings: no dictionary ${declared ? "declared" : "declared in options.uiStrings.locales"} for ${uncovered.join(", ")} — comments and forms on those pages show English text unless ${stringsDir ?? ".contentrain/content/site/ui-strings"}/{locale}.json exists at build`);
|
|
4414
4602
|
}
|
|
4415
4603
|
if ((ir.components ?? []).some((c) => isRuntimeImplemented(c, input.runtime) && c.type === "comments")) {
|
|
4416
|
-
const collections = { ...
|
|
4417
|
-
if (
|
|
4604
|
+
const collections = { ...content.collections };
|
|
4605
|
+
if (content.posts && !collections["posts"]) collections[DEFAULT_COLLECTION] = content.posts;
|
|
4418
4606
|
for (const [name, posts] of Object.entries(collections)) {
|
|
4419
4607
|
const unbound = posts.filter((p) => !p.entry).length;
|
|
4420
4608
|
if (unbound) warnings.push(`collection ${name}: ${unbound} of ${posts.length} posts carry no entry address — the comments component renders nothing on those pages`);
|
|
@@ -4442,9 +4630,17 @@ function emitAstroProject(input) {
|
|
|
4442
4630
|
warnings,
|
|
4443
4631
|
...redirectPlan || hostOnlyPlan ? { redirects: {
|
|
4444
4632
|
written: [...redirectPlan?.written ?? [], ...[...hostOnlyRule.values()].filter((rule) => !unservedHostOnly.has(rule))],
|
|
4445
|
-
manual: [
|
|
4633
|
+
manual: [
|
|
4634
|
+
...redirectPlan?.manual ?? [],
|
|
4635
|
+
...hostOnlyManual,
|
|
4636
|
+
...withheldManual
|
|
4637
|
+
],
|
|
4446
4638
|
host_files: hostRedirects?.written ?? [],
|
|
4447
4639
|
host_over_limit: (hostRedirects?.over_limit ?? []).filter((from) => !hostOnly(from) || namedHost === void 0)
|
|
4640
|
+
} } : {},
|
|
4641
|
+
...published.withheld.length || published.links.length ? { withheld: {
|
|
4642
|
+
entries: published.withheld,
|
|
4643
|
+
links: published.links
|
|
4448
4644
|
} } : {}
|
|
4449
4645
|
};
|
|
4450
4646
|
}
|
|
@@ -4468,6 +4664,6 @@ async function writeEmit(result, dir) {
|
|
|
4468
4664
|
await Promise.all(Object.entries(result.files).map(([path, content]) => writeFile(join(dir, path), content, "utf8")));
|
|
4469
4665
|
}
|
|
4470
4666
|
//#endregion
|
|
4471
|
-
export { EMBED_TS, FEED_ITEMS, FEED_PATH, LLMS_LINKS, REDIRECT_STATUSES, RUNTIME_IMPLEMENTED, SEO_COMPONENT, UI_STRINGS_DIR, UI_STRINGS_MODEL, UI_STRING_DEFAULTS, astroRedirectsConfig, balanceWarning, bodySeoLeaks, builtAddresses, checkBalance, componentMarkers, emitAstroProject, entryPath, hostRedirectFiles, isRuntimeImplemented, linkSources, noindexPaths, pascalCase, patternToPagePath, planRedirects, publisherIdOf, rewriteFeedLinks, seoFromRawEntry, stableJson, stripSeoTags, websiteIdOf, withAlternates, wrapLegacyCss, writeEmit };
|
|
4667
|
+
export { EMBED_TS, FEED_ITEMS, FEED_PATH, LLMS_LINKS, REDIRECT_STATUSES, RUNTIME_IMPLEMENTED, SEO_COMPONENT, UI_STRINGS_DIR, UI_STRINGS_MODEL, UI_STRING_DEFAULTS, astroRedirectsConfig, balanceWarning, bodySeoLeaks, builtAddresses, checkBalance, componentMarkers, emitAstroProject, entryPath, holdReason, hostRedirectFiles, isRuntimeImplemented, linkSources, noindexPaths, pascalCase, patternToPagePath, planRedirects, publishedContent, publisherIdOf, rewriteFeedLinks, seoFromRawEntry, stableJson, stripSeoTags, websiteIdOf, withAlternates, wrapLegacyCss, writeEmit };
|
|
4472
4668
|
|
|
4473
4669
|
//# sourceMappingURL=index.mjs.map
|