@contentrain/emitter-astro 0.17.0 → 0.18.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 +34 -1
- package/dist/index.d.mts +90 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +221 -26
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -185,6 +185,39 @@ The feed lists every post, as WordPress's does — a post the source kept out of
|
|
|
185
185
|
|
|
186
186
|
**Archive feeds.** WordPress serves a feed for every archive page — `/category/news/feed/`, `/tag/x/feed/`, `/author/ada/feed/`. Every dynamic archive route with a query in the site's default language (not its paginated continuation) gets one per page it builds, at `<archive>/feed.xml` (`src/pages/<archive>/feed.xml.ts`): the 10 newest posts that page lists, each at its own address, titled with the page's title. Each archive page links its feed in its head, and `<archive>/feed/` gets a 301 to it in `astro.config` and the host's redirect file, as the main feed does. The feed redirects pass the checks the site's own rules do — none over a page the site builds or a case twin of one, and a source rule for the same address wins — and in a host file with a rule limit they come after the site's own rules, `/feed/` first, so an overflow drops a feed redirect, which the warning counts. The template head's own archive feed link names one term's feed on every page, so it is removed with the other feed links the site does not build (comments, Atom). `options.feed: false` turns these off with the main feed.
|
|
187
187
|
|
|
188
|
+
## Unpublished entries
|
|
189
|
+
|
|
190
|
+
The content store keeps every entry the source had; only the published ones are
|
|
191
|
+
built. `EmitPost.status` is the entry's `EntryMeta.status` (unset reads as
|
|
192
|
+
`published`) and `publish_at` its `EntryMeta.publish_at` — a WordPress post
|
|
193
|
+
scheduled for later is imported as `published` with a `publish_at` ahead. An
|
|
194
|
+
entry is built when its status is `published` and its `publish_at`, if set, is
|
|
195
|
+
not after `options.now` (default: the time of the emit). `EmitPost.visibility`
|
|
196
|
+
(the store's `visibility` field) other than `public` — a password-protected or
|
|
197
|
+
private post — is never built, whatever its status. Every other entry — a
|
|
198
|
+
draft, one in review, rejected or archived, one scheduled for later, one whose
|
|
199
|
+
`publish_at` is not a date, one that is not public — is held back before anything reads the content: no
|
|
200
|
+
page, sitemap line, feed item, llms.txt link, list card or hreflang alternate.
|
|
201
|
+
`EmitResult.withheld.entries` names each with its reason, and a warning counts
|
|
202
|
+
them.
|
|
203
|
+
|
|
204
|
+
An entry with no `status` is published, as before statuses existed — which
|
|
205
|
+
fails open if a producer forgets to pass one. `options.requireStatus: true`
|
|
206
|
+
turns that around: every entry (in posts, collections and list items) must
|
|
207
|
+
carry a status, and one without it is held back (`status_missing`) and
|
|
208
|
+
reported. A producer that reads statuses from the content store sets it.
|
|
209
|
+
|
|
210
|
+
The data files hold only what is live at the emit, so a scheduled entry is
|
|
211
|
+
built by the first emit after its time; rebuilding the same files does not
|
|
212
|
+
publish it.
|
|
213
|
+
|
|
214
|
+
A link on a published page (its body or `excerpt_html`, on its own page or as a
|
|
215
|
+
list card) to a held-back entry would 404: the anchor is replaced by its text
|
|
216
|
+
and listed in `EmitResult.withheld.links`. A link to an address a published
|
|
217
|
+
entry still builds is left alone. A redirect (`redirects` or `hostRedirects`)
|
|
218
|
+
whose target is a held-back entry is not written; it is returned in
|
|
219
|
+
`EmitResult.redirects.manual` with the reason.
|
|
220
|
+
|
|
188
221
|
## Binding the runtime later
|
|
189
222
|
|
|
190
223
|
`src/data/runtime.json` is the only place the binding lives; the components
|
|
@@ -285,4 +318,4 @@ literal routes.
|
|
|
285
318
|
|
|
286
319
|
## Checking the emitted types
|
|
287
320
|
|
|
288
|
-
The unit suite asserts what the emitter writes; `pnpm --filter @contentrain/emitter-astro test:astro` (after `pnpm build`) checks it the way a migrated site's build does. It emits a site with every kind of page and endpoint — posts, pages, nested category and tag archives with their feeds, an author profile, a list, a static page, the feed, llms.txt, redirects — once with trailing slashes and once without, installs it with npm from its own `package.json`, and runs `astro check`. A pass is a result naming 0 errors. CI and the release workflow run it on every change.
|
|
321
|
+
The unit suite asserts what the emitter writes; `pnpm --filter @contentrain/emitter-astro test:astro` (after `pnpm build`) checks it the way a migrated site's build does. It emits a site with every kind of page and endpoint — posts, pages, nested category and tag archives with their feeds, an author profile, a list, a static page, the feed, llms.txt, redirects, and a draft, an entry in review, a post scheduled for later and password-protected and private posts beside published ones — once with trailing slashes and once without, installs it with npm from its own `package.json` the way a production host does (`NODE_ENV=production`, `CI` set, no terminal), and runs `astro check` and then the site's own `npm run build`. A pass is a result naming 0 errors and a completed build; Astro's "requires @astrojs/check" notice fails it even though Astro exits 0 there. The first site is then built with `astro build`, and the output is read: the published posts have pages, sitemap lines and feed items, the held-back ones have none (nor llms.txt links or list cards), no file holds the password-protected post's text, and the live page's links to held-back entries are text. CI and the release workflow run it on every change.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ComponentDef, EntrySourceRef, ModelDefinition, ProjectIR, RawRedirect, RawRedirect as RawRedirect$1, RawSeoEntry, RouteModel, RuntimeBinding, RuntimeBinding as RuntimeBinding$1, SeoProvider } from "@contentrain/types";
|
|
1
|
+
import { ComponentDef, ContentStatus, EntrySourceRef, ModelDefinition, ProjectIR, RawRedirect, RawRedirect as RawRedirect$1, RawSeoEntry, RouteModel, RuntimeBinding, RuntimeBinding as RuntimeBinding$1, SeoProvider } from "@contentrain/types";
|
|
2
2
|
|
|
3
3
|
//#region src/types.d.ts
|
|
4
4
|
/**
|
|
@@ -120,6 +120,49 @@ interface EmitPost {
|
|
|
120
120
|
noindex?: boolean;
|
|
121
121
|
/** The source page was `nofollow`: `<meta name="robots" content="nofollow">`. */
|
|
122
122
|
nofollow?: boolean;
|
|
123
|
+
/**
|
|
124
|
+
* The entry's workflow status in the content store (`EntryMeta.status`).
|
|
125
|
+
* Only `published` is built; unset reads as published unless
|
|
126
|
+
* `EmitOptions.requireStatus` is set. Any other status
|
|
127
|
+
* keeps the entry out of the site — no page, sitemap line, feed item,
|
|
128
|
+
* llms.txt link or list card — and `EmitResult.withheld` names it.
|
|
129
|
+
*/
|
|
130
|
+
status?: ContentStatus;
|
|
131
|
+
/**
|
|
132
|
+
* When a published entry goes live (`EntryMeta.publish_at`, ISO 8601) — a
|
|
133
|
+
* WordPress post scheduled for later. Until then it is held back like a
|
|
134
|
+
* draft; compared with `EmitOptions.now`. A value that is not a date holds
|
|
135
|
+
* the entry back too.
|
|
136
|
+
*/
|
|
137
|
+
publish_at?: string;
|
|
138
|
+
/**
|
|
139
|
+
* Who may read the entry (the store's `visibility` field — WordPress
|
|
140
|
+
* `public`, `private`, or `password` for a password-protected post). Anything
|
|
141
|
+
* but `public` is never built, whatever `status` says: no page, sitemap
|
|
142
|
+
* line, feed item, list card, share card or link to it. Unset reads as public.
|
|
143
|
+
*/
|
|
144
|
+
visibility?: 'public' | 'private' | 'password' | (string & {});
|
|
145
|
+
}
|
|
146
|
+
/** An entry the build leaves out because it is not published yet. */
|
|
147
|
+
interface WithheldEntry {
|
|
148
|
+
collection: string;
|
|
149
|
+
slug: string;
|
|
150
|
+
status?: ContentStatus;
|
|
151
|
+
publish_at?: string;
|
|
152
|
+
visibility?: string;
|
|
153
|
+
/**
|
|
154
|
+
* `visibility`: password-protected or private · `status`: not published ·
|
|
155
|
+
* `status_missing`: no status, with `EmitOptions.requireStatus` ·
|
|
156
|
+
* `scheduled`: publish_at is ahead · `publish_at_invalid`: publish_at is not a date.
|
|
157
|
+
*/
|
|
158
|
+
reason: 'visibility' | 'status' | 'status_missing' | 'scheduled' | 'publish_at_invalid';
|
|
159
|
+
}
|
|
160
|
+
/** A link on a published page to a held-back entry, kept as its text. */
|
|
161
|
+
interface WithheldLink {
|
|
162
|
+
/** The page it was on (site-root path), or `<collection>/<slug>` when no route builds it. */
|
|
163
|
+
page: string;
|
|
164
|
+
/** The address it pointed at. */
|
|
165
|
+
href: string;
|
|
123
166
|
}
|
|
124
167
|
/**
|
|
125
168
|
* One block of a list: a template, optional wrapper, and how many items it takes.
|
|
@@ -345,6 +388,20 @@ interface EmitOptions {
|
|
|
345
388
|
* Cloudflare Pages site must name its host (no forced rules there).
|
|
346
389
|
*/
|
|
347
390
|
redirectHost?: 'netlify' | 'cloudflare' | 'vercel';
|
|
391
|
+
/**
|
|
392
|
+
* The moment `EmitPost.publish_at` is compared with, ISO 8601. Default: the
|
|
393
|
+
* time of the emit. The data files hold only what is live then, so a
|
|
394
|
+
* scheduled entry is built by the first emit after its time.
|
|
395
|
+
*/
|
|
396
|
+
now?: string;
|
|
397
|
+
/**
|
|
398
|
+
* Hold back every entry — in posts, collections and list items — that
|
|
399
|
+
* carries no `EmitPost.status`, and say so. Default false: an entry with
|
|
400
|
+
* no status is published, as before statuses existed. A producer that
|
|
401
|
+
* reads statuses from the content store sets it, so an entry it failed to
|
|
402
|
+
* give one fails closed instead of going out as published.
|
|
403
|
+
*/
|
|
404
|
+
requireStatus?: boolean;
|
|
348
405
|
}
|
|
349
406
|
interface EmitInput {
|
|
350
407
|
ir: ProjectIR;
|
|
@@ -401,6 +458,15 @@ interface EmitResult {
|
|
|
401
458
|
*/
|
|
402
459
|
host_over_limit: string[];
|
|
403
460
|
};
|
|
461
|
+
/**
|
|
462
|
+
* Present when entries were held back (`EmitPost.status`, `publish_at`):
|
|
463
|
+
* each one, and each link to one that a published page carried — kept as
|
|
464
|
+
* its text. Redirects to a held-back entry are in `redirects.manual`.
|
|
465
|
+
*/
|
|
466
|
+
withheld?: {
|
|
467
|
+
entries: WithheldEntry[];
|
|
468
|
+
links: WithheldLink[];
|
|
469
|
+
};
|
|
404
470
|
}
|
|
405
471
|
//#endregion
|
|
406
472
|
//#region src/css.d.ts
|
|
@@ -655,6 +721,28 @@ declare function balanceWarning(html: string): string | null;
|
|
|
655
721
|
/** Site-root paths (directory format, `/a/b/`) of every noindex page, sorted. */
|
|
656
722
|
declare function noindexPaths(routes: RouteModel[], content: EmitContent): string[];
|
|
657
723
|
//#endregion
|
|
724
|
+
//#region src/published.d.ts
|
|
725
|
+
/**
|
|
726
|
+
* Why an entry is not built, or null when it is. `now` is epoch ms. With
|
|
727
|
+
* `requireStatus` an entry that carries no status is held back: the producer
|
|
728
|
+
* said it passes every entry's status, so a missing one is a gap, not a
|
|
729
|
+
* published entry.
|
|
730
|
+
*/
|
|
731
|
+
declare function holdReason(post: Pick<EmitPost, 'status' | 'publish_at' | 'visibility'>, now: number, requireStatus?: boolean): WithheldEntry['reason'] | null;
|
|
732
|
+
interface PublishedContent {
|
|
733
|
+
content: EmitContent;
|
|
734
|
+
withheld: WithheldEntry[];
|
|
735
|
+
links: WithheldLink[];
|
|
736
|
+
/** The addresses held-back entries would have had and no published entry builds (`/a/b/`). */
|
|
737
|
+
gone: Set<string>;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* The content with every unpublished entry removed — from its collection and
|
|
741
|
+
* from the list pages that carry it as an item — and links to them unwrapped.
|
|
742
|
+
* Content with nothing held back is returned as given.
|
|
743
|
+
*/
|
|
744
|
+
declare function publishedContent(routes: RouteModel[], content: EmitContent, now: number, siteUrl?: string, requireStatus?: boolean): PublishedContent;
|
|
745
|
+
//#endregion
|
|
658
746
|
//#region src/redirects.d.ts
|
|
659
747
|
/** The statuses written to `astro.config`; any other is a manual rule. */
|
|
660
748
|
declare const REDIRECT_STATUSES: readonly [301, 302, 307, 308];
|
|
@@ -731,5 +819,5 @@ declare function emitAstroProject(input: EmitInput): EmitResult;
|
|
|
731
819
|
/** Write an EmitResult to disk. Separate from emit so the core stays pure. */
|
|
732
820
|
declare function writeEmit(result: EmitResult, dir: string): Promise<void>;
|
|
733
821
|
//#endregion
|
|
734
|
-
export { type Alternate, type AlternatesResult, type AuthorProfile, type BalanceReport, type Breadcrumb, EMBED_TS, type EmitContent, type EmitCssFile, type EmitInput, type EmitOptions, type EmitPost, type EmitResult, type EmitTermRef, type EntrySourceRef, FEED_ITEMS, FEED_PATH, type FeedLinkResult, type HostRedirectFiles, type ImageMeta, LLMS_LINKS, type LinkSource, type ManualRedirect, type MountRef, type QueryPage, REDIRECT_STATUSES, RUNTIME_IMPLEMENTED, type RawRedirect, type RedirectHost, type RedirectPlan, type RedirectStatus, type RuntimeBinding, SEO_COMPONENT, type SeoFields, type SeoFromRawOptions, type SocialOverride, type StripResult, type TwitterOverride, UI_STRINGS_DIR, UI_STRINGS_MODEL, UI_STRING_DEFAULTS, type UiStringKey, astroRedirectsConfig, balanceWarning, bodySeoLeaks, builtAddresses, checkBalance, componentMarkers, emitAstroProject, entryPath, hostRedirectFiles, isRuntimeImplemented, linkSources, noindexPaths, pascalCase, patternToPagePath, planRedirects, publisherIdOf, rewriteFeedLinks, seoFromRawEntry, stableJson, stripSeoTags, websiteIdOf, withAlternates, wrapLegacyCss, writeEmit };
|
|
822
|
+
export { type Alternate, type AlternatesResult, type AuthorProfile, type BalanceReport, type Breadcrumb, EMBED_TS, type EmitContent, type EmitCssFile, type EmitInput, type EmitOptions, type EmitPost, type EmitResult, type EmitTermRef, type EntrySourceRef, FEED_ITEMS, FEED_PATH, type FeedLinkResult, type HostRedirectFiles, type ImageMeta, LLMS_LINKS, type LinkSource, type ManualRedirect, type MountRef, type PublishedContent, type QueryPage, REDIRECT_STATUSES, RUNTIME_IMPLEMENTED, type RawRedirect, type RedirectHost, type RedirectPlan, type RedirectStatus, type RuntimeBinding, SEO_COMPONENT, type SeoFields, type SeoFromRawOptions, type SocialOverride, type StripResult, type TwitterOverride, UI_STRINGS_DIR, UI_STRINGS_MODEL, UI_STRING_DEFAULTS, type UiStringKey, type WithheldEntry, type WithheldLink, 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 };
|
|
735
823
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/css.ts","../src/feed.ts","../src/util.ts","../src/layouts.ts","../src/components.ts","../src/seo.ts","../src/seo-entry.ts","../src/alternates.ts","../src/embed.ts","../src/ui-strings.ts","../src/balance.ts","../src/noindex.ts","../src/redirects.ts","../src/index.ts"],"mappings":";;;;;AAYA;;;;;AAKA;AAAA,UALiB,WAAA;EACf,IAAA;EACA,IAAA;AAAA;AAAA,UAGe,QAAA;EACf,IAAA;EACA,KAAA;EA8DU;EA5DV,IAAA;EAuFc;;;;;;EAhFd,MAAA,GAAS,MAAA;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/css.ts","../src/feed.ts","../src/util.ts","../src/layouts.ts","../src/components.ts","../src/seo.ts","../src/seo-entry.ts","../src/alternates.ts","../src/embed.ts","../src/ui-strings.ts","../src/balance.ts","../src/noindex.ts","../src/published.ts","../src/redirects.ts","../src/index.ts"],"mappings":"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"}
|
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
|
}
|
|
@@ -3766,7 +3766,7 @@ const REDIRECT_STATUSES = [
|
|
|
3766
3766
|
/** Whitespace, a control character or DEL anywhere in the string. */
|
|
3767
3767
|
const hasControlOrSpace = (value) => [...value].some((char) => char.charCodeAt(0) <= 32 || char.charCodeAt(0) === 127);
|
|
3768
3768
|
/** `/old/` and `/old` are one address in the directory build format. */
|
|
3769
|
-
function addressKey$
|
|
3769
|
+
function addressKey$2(path) {
|
|
3770
3770
|
return path === "/" ? "/" : `${path.replace(/\/+$/, "")}/`;
|
|
3771
3771
|
}
|
|
3772
3772
|
/**
|
|
@@ -3777,7 +3777,7 @@ function addressKey$1(path) {
|
|
|
3777
3777
|
function builtAddresses(routes, content) {
|
|
3778
3778
|
const out = /* @__PURE__ */ new Map();
|
|
3779
3779
|
const put = (path, routeId) => {
|
|
3780
|
-
if (path !== null && !out.has(addressKey$
|
|
3780
|
+
if (path !== null && !out.has(addressKey$2(path))) out.set(addressKey$2(path), routeId);
|
|
3781
3781
|
};
|
|
3782
3782
|
for (const route of routes) {
|
|
3783
3783
|
const pagePath = patternToPagePath(route.pattern);
|
|
@@ -3879,7 +3879,7 @@ function planRedirects(redirects, built, options = {}) {
|
|
|
3879
3879
|
skip("to is not a site-root path or an http(s) URL");
|
|
3880
3880
|
continue;
|
|
3881
3881
|
}
|
|
3882
|
-
const key = addressKey$
|
|
3882
|
+
const key = addressKey$2(from.path);
|
|
3883
3883
|
const owner = built.get(key);
|
|
3884
3884
|
if (owner !== void 0) {
|
|
3885
3885
|
skip(`the migrated site builds a page at ${key} (route ${owner}) — the page is kept`);
|
|
@@ -3901,7 +3901,7 @@ function planRedirects(redirects, built, options = {}) {
|
|
|
3901
3901
|
} catch {
|
|
3902
3902
|
target = redirect.to;
|
|
3903
3903
|
}
|
|
3904
|
-
if (addressKey$
|
|
3904
|
+
if (addressKey$2(target) === key) {
|
|
3905
3905
|
skip("to is the same address as from");
|
|
3906
3906
|
continue;
|
|
3907
3907
|
}
|
|
@@ -4006,6 +4006,172 @@ function hostRedirectFiles(config, host) {
|
|
|
4006
4006
|
};
|
|
4007
4007
|
}
|
|
4008
4008
|
//#endregion
|
|
4009
|
+
//#region src/published.ts
|
|
4010
|
+
/**
|
|
4011
|
+
* Why an entry is not built, or null when it is. `now` is epoch ms. With
|
|
4012
|
+
* `requireStatus` an entry that carries no status is held back: the producer
|
|
4013
|
+
* said it passes every entry's status, so a missing one is a gap, not a
|
|
4014
|
+
* published entry.
|
|
4015
|
+
*/
|
|
4016
|
+
function holdReason(post, now, requireStatus = false) {
|
|
4017
|
+
if (post.visibility !== void 0 && post.visibility !== "public") return "visibility";
|
|
4018
|
+
if (post.status === void 0 && requireStatus) return "status_missing";
|
|
4019
|
+
if (post.status !== void 0 && post.status !== "published") return "status";
|
|
4020
|
+
if (post.publish_at === void 0) return null;
|
|
4021
|
+
const at = Date.parse(post.publish_at);
|
|
4022
|
+
if (Number.isNaN(at)) return "publish_at_invalid";
|
|
4023
|
+
return at > now ? "scheduled" : null;
|
|
4024
|
+
}
|
|
4025
|
+
const bareHost = (host) => host.replace(/^www\./, "").toLowerCase();
|
|
4026
|
+
/** `/a/b` and `/a/b/` are one address. */
|
|
4027
|
+
const addressKey$1 = (path) => path === "/" ? "/" : `${path.replace(/\/+$/, "")}/`;
|
|
4028
|
+
/**
|
|
4029
|
+
* The held-back address an href points at, or null: a site-root path, or an
|
|
4030
|
+
* absolute URL on the site's own host (`www.` either way).
|
|
4031
|
+
*/
|
|
4032
|
+
function withheldTarget(href, gone, siteUrl) {
|
|
4033
|
+
if (!gone.size) return null;
|
|
4034
|
+
let site;
|
|
4035
|
+
try {
|
|
4036
|
+
site = siteUrl ? new URL(siteUrl) : void 0;
|
|
4037
|
+
} catch {
|
|
4038
|
+
site = void 0;
|
|
4039
|
+
}
|
|
4040
|
+
let url;
|
|
4041
|
+
try {
|
|
4042
|
+
url = new URL(href, site ?? "http://site.invalid");
|
|
4043
|
+
} catch {
|
|
4044
|
+
return null;
|
|
4045
|
+
}
|
|
4046
|
+
if (!(site ? bareHost(url.host) === bareHost(site.host) : url.host === "site.invalid") || !/^https?:$/.test(url.protocol)) return null;
|
|
4047
|
+
let path = url.pathname;
|
|
4048
|
+
try {
|
|
4049
|
+
path = decodeURI(path);
|
|
4050
|
+
} catch {}
|
|
4051
|
+
return gone.has(addressKey$1(path)) ? path : null;
|
|
4052
|
+
}
|
|
4053
|
+
/**
|
|
4054
|
+
* The content with every unpublished entry removed — from its collection and
|
|
4055
|
+
* from the list pages that carry it as an item — and links to them unwrapped.
|
|
4056
|
+
* Content with nothing held back is returned as given.
|
|
4057
|
+
*/
|
|
4058
|
+
function publishedContent(routes, content, now, siteUrl, requireStatus = false) {
|
|
4059
|
+
const withheld = [];
|
|
4060
|
+
const keep = (collection) => (post) => {
|
|
4061
|
+
const reason = holdReason(post, now, requireStatus);
|
|
4062
|
+
if (!reason) return true;
|
|
4063
|
+
withheld.push({
|
|
4064
|
+
collection,
|
|
4065
|
+
slug: post.slug,
|
|
4066
|
+
...post.status ? { status: post.status } : {},
|
|
4067
|
+
...post.visibility ? { visibility: post.visibility } : {},
|
|
4068
|
+
...post.publish_at ? { publish_at: post.publish_at } : {},
|
|
4069
|
+
reason
|
|
4070
|
+
});
|
|
4071
|
+
return false;
|
|
4072
|
+
};
|
|
4073
|
+
const collections = content.collections ? Object.fromEntries(Object.entries(content.collections).map(([name, posts]) => [name, posts.filter(keep(name))])) : void 0;
|
|
4074
|
+
const isLive = (post) => holdReason(post, now, requireStatus) === null;
|
|
4075
|
+
const posts = content.posts?.filter(content.collections?.["posts"] ? isLive : keep(DEFAULT_COLLECTION));
|
|
4076
|
+
const queries = content.queries ? Object.fromEntries(Object.entries(content.queries).map(([id, pages]) => [id, pages.map((page) => page.items.every(isLive) ? page : {
|
|
4077
|
+
...page,
|
|
4078
|
+
items: page.items.filter(isLive)
|
|
4079
|
+
})])) : void 0;
|
|
4080
|
+
const listedOnly = Object.values(content.queries ?? {}).flat().flatMap((p) => p.items).some((item) => !isLive(item));
|
|
4081
|
+
if (!withheld.length && !listedOnly) return {
|
|
4082
|
+
content,
|
|
4083
|
+
withheld,
|
|
4084
|
+
links: [],
|
|
4085
|
+
gone: /* @__PURE__ */ new Set()
|
|
4086
|
+
};
|
|
4087
|
+
const filtered = {
|
|
4088
|
+
...content,
|
|
4089
|
+
...posts ? { posts } : {},
|
|
4090
|
+
...collections ? { collections } : {},
|
|
4091
|
+
...queries ? { queries } : {}
|
|
4092
|
+
};
|
|
4093
|
+
const built = builtAddresses(routes, filtered);
|
|
4094
|
+
const gone = /* @__PURE__ */ new Set();
|
|
4095
|
+
for (const route of routes) {
|
|
4096
|
+
const pagePath = patternToPagePath(route.pattern);
|
|
4097
|
+
if (!pagePath || !pagePath.includes("[") || route.kind !== "single" && route.collection === void 0) continue;
|
|
4098
|
+
const collection = route.collection ?? "posts";
|
|
4099
|
+
for (const post of collectionItems(content, collection)) {
|
|
4100
|
+
const path = entryPath(route.pattern, {
|
|
4101
|
+
...post.params,
|
|
4102
|
+
slug: post.slug
|
|
4103
|
+
});
|
|
4104
|
+
if (path && !isLive(post) && !built.has(addressKey$1(path))) gone.add(addressKey$1(path));
|
|
4105
|
+
}
|
|
4106
|
+
}
|
|
4107
|
+
const links = [];
|
|
4108
|
+
const unlink = (html, page) => {
|
|
4109
|
+
if (!html || !gone.size) return html;
|
|
4110
|
+
return html.replace(ANCHOR_RE, (anchor, double, single, inner) => {
|
|
4111
|
+
const to = withheldTarget((double ?? single ?? "").replace(/&/g, "&"), gone, siteUrl);
|
|
4112
|
+
if (!to) return anchor;
|
|
4113
|
+
links.push({
|
|
4114
|
+
page,
|
|
4115
|
+
href: to
|
|
4116
|
+
});
|
|
4117
|
+
return inner;
|
|
4118
|
+
});
|
|
4119
|
+
};
|
|
4120
|
+
const unlinkPost = (post, page) => {
|
|
4121
|
+
const body = unlink(post.body, page) ?? post.body;
|
|
4122
|
+
const excerpt_html = unlink(post.excerpt_html, page);
|
|
4123
|
+
return body === post.body && excerpt_html === post.excerpt_html ? post : {
|
|
4124
|
+
...post,
|
|
4125
|
+
body,
|
|
4126
|
+
...excerpt_html !== void 0 ? { excerpt_html } : {}
|
|
4127
|
+
};
|
|
4128
|
+
};
|
|
4129
|
+
const pageOf = (collection, post) => {
|
|
4130
|
+
const route = routes.find((r) => (r.kind === "single" || r.collection !== void 0) && (r.collection ?? "posts") === collection);
|
|
4131
|
+
return (route && entryPath(route.pattern, {
|
|
4132
|
+
...post.params,
|
|
4133
|
+
slug: post.slug
|
|
4134
|
+
})) ?? `${collection}/${post.slug}`;
|
|
4135
|
+
};
|
|
4136
|
+
if (filtered.collections) filtered.collections = Object.fromEntries(Object.entries(filtered.collections).map(([name, list]) => [name, list.map((p) => unlinkPost(p, pageOf(name, p)))]));
|
|
4137
|
+
if (filtered.posts) filtered.posts = filtered.posts.map((p) => unlinkPost(p, pageOf(DEFAULT_COLLECTION, p)));
|
|
4138
|
+
if (filtered.queries) filtered.queries = Object.fromEntries(Object.entries(filtered.queries).map(([id, pages]) => [id, pages.map((page) => {
|
|
4139
|
+
const route = routes.find((r) => r.query === id);
|
|
4140
|
+
const at = (route && entryPath(route.pattern, page.params)) ?? `query ${id}`;
|
|
4141
|
+
return {
|
|
4142
|
+
...page,
|
|
4143
|
+
items: page.items.map((item) => unlinkPost(item, at))
|
|
4144
|
+
};
|
|
4145
|
+
})]));
|
|
4146
|
+
return {
|
|
4147
|
+
content: filtered,
|
|
4148
|
+
withheld,
|
|
4149
|
+
links,
|
|
4150
|
+
gone
|
|
4151
|
+
};
|
|
4152
|
+
}
|
|
4153
|
+
const ANCHOR_RE = /<a\b[^>]*?\shref\s*=\s*(?:"([^"]*)"|'([^']*)')[^>]*>([\s\S]*?)<\/a\s*>/gi;
|
|
4154
|
+
/** The report lines for what was held back. */
|
|
4155
|
+
function withheldWarnings(withheld, links) {
|
|
4156
|
+
const out = [];
|
|
4157
|
+
if (withheld.length) {
|
|
4158
|
+
const count = (reason) => withheld.filter((w) => w.reason === reason).length;
|
|
4159
|
+
const parts = [
|
|
4160
|
+
count("visibility") && `${count("visibility")} password-protected or private`,
|
|
4161
|
+
count("status_missing") && `${count("status_missing")} with no status (options.requireStatus)`,
|
|
4162
|
+
count("status") && `${count("status")} not published (draft, in review, rejected or archived)`,
|
|
4163
|
+
count("scheduled") && `${count("scheduled")} scheduled for later — built by the first emit after their publish_at`,
|
|
4164
|
+
count("publish_at_invalid") && `${count("publish_at_invalid")} with a publish_at that is not a date`
|
|
4165
|
+
].filter(Boolean);
|
|
4166
|
+
out.push(`unpublished: ${withheld.length} entries not built — ${parts.join("; ")}. They stay in the content store; EmitResult.withheld lists each`);
|
|
4167
|
+
}
|
|
4168
|
+
if (links.length) {
|
|
4169
|
+
const sample = links.slice(0, 5).map((l) => `${l.href} (on ${l.page})`).join(", ");
|
|
4170
|
+
out.push(`unpublished: ${links.length} links from published pages to unpublished entries kept as plain text — ${sample}${links.length > 5 ? " …" : ""}`);
|
|
4171
|
+
}
|
|
4172
|
+
return out;
|
|
4173
|
+
}
|
|
4174
|
+
//#endregion
|
|
4009
4175
|
//#region src/seo-entry.ts
|
|
4010
4176
|
/**
|
|
4011
4177
|
* A plugin's unrendered template, in its own syntax: Yoast `%%title%%`,
|
|
@@ -4184,6 +4350,27 @@ function emitAstroProject(input) {
|
|
|
4184
4350
|
files[path] = content;
|
|
4185
4351
|
}
|
|
4186
4352
|
};
|
|
4353
|
+
let now = Date.now();
|
|
4354
|
+
if (input.options?.now !== void 0) {
|
|
4355
|
+
const at = Date.parse(input.options.now);
|
|
4356
|
+
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`);
|
|
4357
|
+
else now = at;
|
|
4358
|
+
}
|
|
4359
|
+
const published = publishedContent(ir.routes, input.content ?? {}, now, ir.site.url, input.options?.requireStatus === true);
|
|
4360
|
+
const content = published.content;
|
|
4361
|
+
warnings.push(...withheldWarnings(published.withheld, published.links));
|
|
4362
|
+
const withheldManual = [];
|
|
4363
|
+
const toPublished = (rules) => rules?.filter((redirect) => {
|
|
4364
|
+
const target = withheldTarget(redirect.to, published.gone, ir.site.url);
|
|
4365
|
+
if (target) withheldManual.push({
|
|
4366
|
+
redirect,
|
|
4367
|
+
reason: `to ${target}, an entry that is not published — the build has no page there`
|
|
4368
|
+
});
|
|
4369
|
+
return !target;
|
|
4370
|
+
});
|
|
4371
|
+
const siteRedirects = toPublished(input.redirects);
|
|
4372
|
+
const hostOnlyRedirects = toPublished(input.hostRedirects);
|
|
4373
|
+
if (withheldManual.length) warnings.push(`unpublished: ${withheldManual.length} redirects point at unpublished entries — not written (EmitResult.redirects.manual has each)`);
|
|
4187
4374
|
const siteLang = ir.site.locales?.[0] ?? "en";
|
|
4188
4375
|
const linkable = ir.site.url ? linkSources(ir.routes, siteLang) : [];
|
|
4189
4376
|
const feedSource = input.options?.feed !== false ? linkable.find((s) => s.collection === DEFAULT_COLLECTION) : void 0;
|
|
@@ -4191,11 +4378,11 @@ function emitAstroProject(input) {
|
|
|
4191
4378
|
const archiveFeeds = feedSource ? archiveFeedSources(ir.routes, siteLang) : [];
|
|
4192
4379
|
const archiveFeedRoutes = new Set(archiveFeeds.map((a) => a.routeId));
|
|
4193
4380
|
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 ${
|
|
4381
|
+
const redirectPlan = siteRedirects ? planRedirects(siteRedirects, builtAddresses(ir.routes, content)) : void 0;
|
|
4382
|
+
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
4383
|
const seo = input.options?.seo !== false;
|
|
4197
|
-
const noindex = noindexPaths(ir.routes,
|
|
4198
|
-
const built = builtAddresses(ir.routes,
|
|
4384
|
+
const noindex = noindexPaths(ir.routes, content);
|
|
4385
|
+
const built = builtAddresses(ir.routes, content);
|
|
4199
4386
|
const sourceConfig = redirectPlan?.config ?? {};
|
|
4200
4387
|
const claimed = new Set(Object.keys(sourceConfig).map((from) => from.replace(/\/+$/, "").toLowerCase()));
|
|
4201
4388
|
const feedRules = [];
|
|
@@ -4210,7 +4397,7 @@ function emitAstroProject(input) {
|
|
|
4210
4397
|
});
|
|
4211
4398
|
};
|
|
4212
4399
|
if (feedSource) addFeedRule(FEED_REDIRECT_FROM, FEED_PATH);
|
|
4213
|
-
for (const source of archiveFeeds) for (const page of
|
|
4400
|
+
for (const source of archiveFeeds) for (const page of content.queries?.[source.query] ?? []) {
|
|
4214
4401
|
const address = entryPath(source.pattern, page.params);
|
|
4215
4402
|
if (address) addFeedRule(`${address}feed/`, `${address}feed.xml`);
|
|
4216
4403
|
}
|
|
@@ -4222,7 +4409,7 @@ function emitAstroProject(input) {
|
|
|
4222
4409
|
};
|
|
4223
4410
|
const hasRedirects = Object.keys(redirectConfig).length > 0;
|
|
4224
4411
|
add(scaffoldFiles(ir, input.options ?? {}, noindex, hasRedirects ? astroRedirectsConfig(sortedConfig(redirectConfig)) : null, input.runtime));
|
|
4225
|
-
const hostOnlyPlan =
|
|
4412
|
+
const hostOnlyPlan = hostOnlyRedirects ? planRedirects(hostOnlyRedirects, built, { hostOnly: true }) : void 0;
|
|
4226
4413
|
const hostOnlyConfig = {};
|
|
4227
4414
|
const hostOnlyRule = /* @__PURE__ */ new Map();
|
|
4228
4415
|
const hostOnlyManual = [...hostOnlyPlan?.manual ?? []];
|
|
@@ -4264,7 +4451,7 @@ function emitAstroProject(input) {
|
|
|
4264
4451
|
});
|
|
4265
4452
|
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
4453
|
const unservedHostOnly = new Set(hostOnlyManual.map((m) => m.redirect));
|
|
4267
|
-
if (hostOnlyManual.length) warnings.push(`hostRedirects: ${hostOnlyManual.length} of ${
|
|
4454
|
+
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
4455
|
if (hostRedirects) {
|
|
4269
4456
|
add(hostRedirects.files);
|
|
4270
4457
|
const overLimit = hostRedirects.over_limit.filter((from) => !hostOnly(from));
|
|
@@ -4300,7 +4487,7 @@ function emitAstroProject(input) {
|
|
|
4300
4487
|
const bodyMarkersByFamily = /* @__PURE__ */ new Map();
|
|
4301
4488
|
for (const route of ir.routes) {
|
|
4302
4489
|
if (route.kind !== "single" && route.collection === void 0) continue;
|
|
4303
|
-
const posts = collectionItems(
|
|
4490
|
+
const posts = collectionItems(content, route.collection ?? "posts");
|
|
4304
4491
|
const ids = bodyMarkersByFamily.get(route.family) ?? /* @__PURE__ */ new Set();
|
|
4305
4492
|
for (const post of posts) for (const id of componentMarkers(post.body)) ids.add(id);
|
|
4306
4493
|
if (ids.size) bodyMarkersByFamily.set(route.family, ids);
|
|
@@ -4309,7 +4496,7 @@ function emitAstroProject(input) {
|
|
|
4309
4496
|
const queryBound = /* @__PURE__ */ new Set();
|
|
4310
4497
|
for (const family of ir.families) for (const placement of family.components ?? []) {
|
|
4311
4498
|
if (!placement.query) continue;
|
|
4312
|
-
const pages =
|
|
4499
|
+
const pages = content.queries?.[placement.query];
|
|
4313
4500
|
if (!pages) {
|
|
4314
4501
|
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
4502
|
continue;
|
|
@@ -4342,23 +4529,23 @@ function emitAstroProject(input) {
|
|
|
4342
4529
|
for (const ref of refs ?? []) if (!providedCss.has(ref.split("/").pop() ?? ref)) warnings.push(`${owner}: css file "${ref}" not provided in input.css`);
|
|
4343
4530
|
};
|
|
4344
4531
|
for (const family of ir.families) missingCss(family.css.files, `family ${family.id}`);
|
|
4345
|
-
const allPosts = [...
|
|
4532
|
+
const allPosts = [...content.posts ?? [], ...Object.values(content.collections ?? {}).flat()];
|
|
4346
4533
|
for (const post of allPosts) missingCss(post.css, `post ${post.slug}`);
|
|
4347
|
-
for (const [queryId, queryPages] of Object.entries(
|
|
4348
|
-
const trailOwners = [...Object.entries(
|
|
4534
|
+
for (const [queryId, queryPages] of Object.entries(content.queries ?? {})) for (const qp of queryPages) missingCss(qp.css, `query ${queryId}`);
|
|
4535
|
+
const trailOwners = [...Object.entries(content.collections ?? {}), ...content.posts ? [[DEFAULT_COLLECTION, content.posts]] : []];
|
|
4349
4536
|
for (const [name, posts] of trailOwners) {
|
|
4350
4537
|
const bad = (posts ?? []).filter((p) => badTrail(p.breadcrumbs)).length;
|
|
4351
4538
|
if (bad) warnings.push(`collection ${name}: ${bad} breadcrumb trails dropped — every crumb needs a name and a site-root-relative path`);
|
|
4352
4539
|
const badLd = (posts ?? []).filter((p) => badSchema(p.schema)).length;
|
|
4353
4540
|
if (badLd) warnings.push(`collection ${name}: ${badLd} schema values are not JSON-LD objects — those pages print the generated structured data instead`);
|
|
4354
4541
|
}
|
|
4355
|
-
for (const [queryId, queryPages] of Object.entries(
|
|
4542
|
+
for (const [queryId, queryPages] of Object.entries(content.queries ?? {})) {
|
|
4356
4543
|
const bad = queryPages.filter((qp) => badTrail(qp.breadcrumbs)).length;
|
|
4357
4544
|
if (bad) warnings.push(`query ${queryId}: ${bad} breadcrumb trails dropped — every crumb needs a name and a site-root-relative path`);
|
|
4358
4545
|
const badLd = queryPages.filter((qp) => badSchema(qp.schema)).length;
|
|
4359
4546
|
if (badLd) warnings.push(`query ${queryId}: ${badLd} schema values are not JSON-LD objects — those pages print the generated structured data instead`);
|
|
4360
4547
|
}
|
|
4361
|
-
let routeContent =
|
|
4548
|
+
let routeContent = content;
|
|
4362
4549
|
if (seo) {
|
|
4363
4550
|
const alternates = withAlternates(ir.routes, routeContent, lang);
|
|
4364
4551
|
routeContent = alternates.content;
|
|
@@ -4413,8 +4600,8 @@ function emitAstroProject(input) {
|
|
|
4413
4600
|
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
4601
|
}
|
|
4415
4602
|
if ((ir.components ?? []).some((c) => isRuntimeImplemented(c, input.runtime) && c.type === "comments")) {
|
|
4416
|
-
const collections = { ...
|
|
4417
|
-
if (
|
|
4603
|
+
const collections = { ...content.collections };
|
|
4604
|
+
if (content.posts && !collections["posts"]) collections[DEFAULT_COLLECTION] = content.posts;
|
|
4418
4605
|
for (const [name, posts] of Object.entries(collections)) {
|
|
4419
4606
|
const unbound = posts.filter((p) => !p.entry).length;
|
|
4420
4607
|
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 +4629,17 @@ function emitAstroProject(input) {
|
|
|
4442
4629
|
warnings,
|
|
4443
4630
|
...redirectPlan || hostOnlyPlan ? { redirects: {
|
|
4444
4631
|
written: [...redirectPlan?.written ?? [], ...[...hostOnlyRule.values()].filter((rule) => !unservedHostOnly.has(rule))],
|
|
4445
|
-
manual: [
|
|
4632
|
+
manual: [
|
|
4633
|
+
...redirectPlan?.manual ?? [],
|
|
4634
|
+
...hostOnlyManual,
|
|
4635
|
+
...withheldManual
|
|
4636
|
+
],
|
|
4446
4637
|
host_files: hostRedirects?.written ?? [],
|
|
4447
4638
|
host_over_limit: (hostRedirects?.over_limit ?? []).filter((from) => !hostOnly(from) || namedHost === void 0)
|
|
4639
|
+
} } : {},
|
|
4640
|
+
...published.withheld.length || published.links.length ? { withheld: {
|
|
4641
|
+
entries: published.withheld,
|
|
4642
|
+
links: published.links
|
|
4448
4643
|
} } : {}
|
|
4449
4644
|
};
|
|
4450
4645
|
}
|
|
@@ -4468,6 +4663,6 @@ async function writeEmit(result, dir) {
|
|
|
4468
4663
|
await Promise.all(Object.entries(result.files).map(([path, content]) => writeFile(join(dir, path), content, "utf8")));
|
|
4469
4664
|
}
|
|
4470
4665
|
//#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 };
|
|
4666
|
+
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
4667
|
|
|
4473
4668
|
//# sourceMappingURL=index.mjs.map
|