@mdwrk/lander-core 0.1.2

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 ADDED
@@ -0,0 +1,50 @@
1
+ # @mdwrk/lander-core
2
+
3
+ **Content compiler and diagnostics**
4
+
5
+ <p align="center">
6
+ <a href="https://github.com/groupsum/markdown_workspace/blob/master/packages/lander/lander-core/README.md"><img alt="Hits" src="https://visitor-badge.laobi.icu/badge?page_id=groupsum.markdown_workspace.packages_lander_lander_core_README&amp;left_text=hits" /></a>
7
+ <a href="https://www.npmjs.com/package/@mdwrk/lander-core"><img alt="Downloads" src="https://img.shields.io/npm/dm/%40mdwrk%2Flander-core?label=downloads" /></a>
8
+ <a href="../../../package.json"><img alt="Node" src="https://img.shields.io/badge/node-20.x%20%7C%2021.x%20%7C%2022.x-339933?logo=node.js&amp;logoColor=white" /></a>
9
+ <a href="../../../LICENSE"><img alt="License: Apache-2.0" src="https://img.shields.io/badge/license-Apache--2.0-blue.svg" /></a>
10
+ </p>
11
+
12
+ This package compiles lander content, validates it, derives breadcrumbs and internal links, and generates sitemap, robots, and llms.txt outputs.
13
+
14
+ ## Why
15
+ Use it when you need the compile-time logic behind a product lander without committing to a UI layer yet.
16
+
17
+ ## What
18
+ - Site definition helpers and validation.
19
+ - Compiled page metadata such as breadcrumbs, internal links, and word counts.
20
+ - Sitemap, robots.txt, and llms.txt generation.
21
+
22
+ ## Installation
23
+ Node.js 20.x through 22.x, matching the workspace engine contract in the root package manifest.
24
+
25
+ ```bash
26
+ npm install @mdwrk/lander-core @mdwrk/lander-content-contract
27
+ ```
28
+
29
+ ## Usage
30
+ ```ts
31
+ import { compileLanderSite, defineLanderSite } from "@mdwrk/lander-core";
32
+
33
+ const site = defineLanderSite({
34
+ product: {
35
+ name: "MdWrk",
36
+ slug: "mdwrk",
37
+ tagline: "Markdown workspace platform",
38
+ description: "Portable markdown authoring and publishing.",
39
+ category: "DeveloperTool",
40
+ canonicalUrl: "https://mdwrk.com",
41
+ },
42
+ pages: [],
43
+ });
44
+
45
+ const compiled = compileLanderSite(site);
46
+ ```
47
+
48
+ ## Related
49
+ - [Packages index](../../README.md) - family and package navigation
50
+ - [Root README](../../../README.md) - repo overview
@@ -0,0 +1,31 @@
1
+ export type LanderCacheResourceClass = "immutable" | "mutable-revalidate" | "no-store";
2
+ export interface LanderCacheResourceInput {
3
+ path: string;
4
+ content: string | Uint8Array;
5
+ resourceClass?: LanderCacheResourceClass;
6
+ contentType?: string;
7
+ lastModified?: Date | string;
8
+ }
9
+ export interface LanderCacheManifestEntry {
10
+ path: string;
11
+ resourceClass: LanderCacheResourceClass;
12
+ contentType?: string;
13
+ etag: string;
14
+ lastModified: string;
15
+ cacheControl: string;
16
+ headers: Record<string, string>;
17
+ }
18
+ export interface LanderCacheHeaderManifest {
19
+ version: 1;
20
+ generatedAt: string;
21
+ entries: LanderCacheManifestEntry[];
22
+ }
23
+ export declare function normalizeCacheResourcePath(value: string): string;
24
+ export declare function sha256Hex(content: string | Uint8Array): string;
25
+ export declare function buildStrongEtag(content: string | Uint8Array): string;
26
+ export declare function formatHttpDate(value: Date | string | undefined): string;
27
+ export declare function inferCacheResourceClass(pathValue: string): LanderCacheResourceClass;
28
+ export declare function cacheControlForResourceClass(resourceClass: LanderCacheResourceClass): string;
29
+ export declare function buildCacheManifestEntry(input: LanderCacheResourceInput): LanderCacheManifestEntry;
30
+ export declare function buildCacheHeaderManifest(inputs: LanderCacheResourceInput[], generatedAt?: Date | string): LanderCacheHeaderManifest;
31
+ //# sourceMappingURL=resource-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-policy.d.ts","sourceRoot":"","sources":["../../src/cache/resource-policy.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,oBAAoB,GAAG,UAAU,CAAC;AAEvF,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,UAAU,CAAC;IAC7B,aAAa,CAAC,EAAE,wBAAwB,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,aAAa,EAAE,wBAAwB,CAAC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,CAAC,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,wBAAwB,EAAE,CAAC;CACrC;AAID,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIhE;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAE9D;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAEpE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAMvE;AAED,wBAAgB,uBAAuB,CAAC,SAAS,EAAE,MAAM,GAAG,wBAAwB,CAYnF;AAED,wBAAgB,4BAA4B,CAAC,aAAa,EAAE,wBAAwB,GAAG,MAAM,CAW5F;AAED,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,wBAAwB,GAAG,wBAAwB,CAwBjG;AAED,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,wBAAwB,EAAE,EAAE,WAAW,GAAE,IAAI,GAAG,MAAmB,GAAG,yBAAyB,CAQ/I"}
@@ -0,0 +1,84 @@
1
+ // @ts-ignore This package may compile without Node ambient types, but its ESM runtime is Node.
2
+ import { createHash } from "node:crypto";
3
+ const IMMUTABLE_MAX_AGE_SECONDS = 31_536_000;
4
+ export function normalizeCacheResourcePath(value) {
5
+ const path = String(value ?? "").trim().replace(/\\/g, "/");
6
+ if (!path)
7
+ return "/";
8
+ return path.startsWith("/") ? path : `/${path}`;
9
+ }
10
+ export function sha256Hex(content) {
11
+ return createHash("sha256").update(content).digest("hex");
12
+ }
13
+ export function buildStrongEtag(content) {
14
+ return `"sha256-${sha256Hex(content)}"`;
15
+ }
16
+ export function formatHttpDate(value) {
17
+ const date = value instanceof Date ? value : value ? new Date(value) : new Date();
18
+ if (Number.isNaN(date.valueOf())) {
19
+ throw new Error(`Invalid HTTP date value: ${String(value)}`);
20
+ }
21
+ return date.toUTCString();
22
+ }
23
+ export function inferCacheResourceClass(pathValue) {
24
+ const resourcePath = normalizeCacheResourcePath(pathValue);
25
+ if (/\/assets\/[^/]+\.[a-f0-9]{8,}\.(?:css|js|mjs|png|jpg|jpeg|webp|svg|ico|woff2?)$/i.test(resourcePath)) {
26
+ return "immutable";
27
+ }
28
+ if (/\/(?:sitemap\.xml|robots\.txt|llms(?:-full)?\.txt|content-index\.json|content-registry\.json|jsonld-graph\.json|cache-header-manifest\.json)$/i.test(resourcePath)) {
29
+ return "mutable-revalidate";
30
+ }
31
+ if (/\/index\.(?:html|md)$/i.test(resourcePath) || resourcePath.endsWith("/")) {
32
+ return "mutable-revalidate";
33
+ }
34
+ return "mutable-revalidate";
35
+ }
36
+ export function cacheControlForResourceClass(resourceClass) {
37
+ switch (resourceClass) {
38
+ case "immutable":
39
+ return `public, max-age=${IMMUTABLE_MAX_AGE_SECONDS}, immutable`;
40
+ case "mutable-revalidate":
41
+ return "no-cache";
42
+ case "no-store":
43
+ return "no-store";
44
+ default:
45
+ return assertNever(resourceClass);
46
+ }
47
+ }
48
+ export function buildCacheManifestEntry(input) {
49
+ const resourceClass = input.resourceClass ?? inferCacheResourceClass(input.path);
50
+ const cacheControl = cacheControlForResourceClass(resourceClass);
51
+ const lastModified = formatHttpDate(input.lastModified);
52
+ const etag = buildStrongEtag(input.content);
53
+ const headers = {
54
+ "Cache-Control": cacheControl,
55
+ };
56
+ if (resourceClass !== "no-store") {
57
+ headers.ETag = etag;
58
+ headers["Last-Modified"] = lastModified;
59
+ }
60
+ if (input.contentType)
61
+ headers["Content-Type"] = input.contentType;
62
+ return {
63
+ path: normalizeCacheResourcePath(input.path),
64
+ resourceClass,
65
+ contentType: input.contentType,
66
+ etag,
67
+ lastModified,
68
+ cacheControl,
69
+ headers,
70
+ };
71
+ }
72
+ export function buildCacheHeaderManifest(inputs, generatedAt = new Date()) {
73
+ return {
74
+ version: 1,
75
+ generatedAt: formatHttpDate(generatedAt),
76
+ entries: inputs
77
+ .map(buildCacheManifestEntry)
78
+ .sort((left, right) => left.path.localeCompare(right.path)),
79
+ };
80
+ }
81
+ function assertNever(value) {
82
+ throw new Error(`Unsupported cache resource class: ${String(value)}`);
83
+ }
84
+ //# sourceMappingURL=resource-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-policy.js","sourceRoot":"","sources":["../../src/cache/resource-policy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface LanderCriticalCssProfile {
2
+ id: string;
3
+ css: string;
4
+ }
5
+ export declare function defineCriticalCssProfile<T extends LanderCriticalCssProfile>(profile: T): T;
6
+ export declare function normalizeCriticalCss(css: string): string;
7
+ export declare function renderCriticalCssStyle(profile: LanderCriticalCssProfile): string;
8
+ export declare function renderDeferredStylesheetLink(href: string): string;
9
+ //# sourceMappingURL=profile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.d.ts","sourceRoot":"","sources":["../../src/critical-css/profile.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,wBAAwB,CAAC,CAAC,SAAS,wBAAwB,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAI1F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAMxD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,wBAAwB,GAAG,MAAM,CAGhF;AAED,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAMjE"}
@@ -0,0 +1,34 @@
1
+ export function defineCriticalCssProfile(profile) {
2
+ if (!profile.id.trim())
3
+ throw new Error("Critical CSS profile id is required.");
4
+ if (!profile.css.trim())
5
+ throw new Error(`Critical CSS profile ${profile.id} must include CSS.`);
6
+ return profile;
7
+ }
8
+ export function normalizeCriticalCss(css) {
9
+ return String(css ?? "")
10
+ .replace(/\/\*[\s\S]*?\*\//g, "")
11
+ .replace(/\s+/g, " ")
12
+ .replace(/\s*([{}:;,>])\s*/g, "$1")
13
+ .trim();
14
+ }
15
+ export function renderCriticalCssStyle(profile) {
16
+ const css = normalizeCriticalCss(profile.css);
17
+ return `<style data-lander-critical-css="${escapeAttribute(profile.id)}">${css}</style>`;
18
+ }
19
+ export function renderDeferredStylesheetLink(href) {
20
+ const escapedHref = escapeAttribute(href);
21
+ return [
22
+ `<link rel="preload" href="${escapedHref}" as="style" onload="this.onload=null;this.rel='stylesheet'">`,
23
+ `<noscript><link rel="stylesheet" href="${escapedHref}"></noscript>`,
24
+ ].join("");
25
+ }
26
+ function escapeAttribute(value) {
27
+ return String(value ?? "")
28
+ .replace(/&/g, "&amp;")
29
+ .replace(/</g, "&lt;")
30
+ .replace(/>/g, "&gt;")
31
+ .replace(/"/g, "&quot;")
32
+ .replace(/'/g, "&#39;");
33
+ }
34
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../../src/critical-css/profile.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,wBAAwB,CAAqC,OAAU;IACrF,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAChF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,OAAO,CAAC,EAAE,oBAAoB,CAAC,CAAC;IACjG,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,GAAW;IAC9C,OAAO,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;SACrB,OAAO,CAAC,mBAAmB,EAAE,EAAE,CAAC;SAChC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC;SAClC,IAAI,EAAE,CAAC;AACZ,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAAiC;IACtE,MAAM,GAAG,GAAG,oBAAoB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IAC9C,OAAO,oCAAoC,eAAe,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,GAAG,UAAU,CAAC;AAC3F,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,IAAY;IACvD,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IAC1C,OAAO;QACL,6BAA6B,WAAW,+DAA+D;QACvG,0CAA0C,WAAW,eAAe;KACrE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACb,CAAC;AAED,SAAS,eAAe,CAAC,KAAa;IACpC,OAAO,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { LanderSite, PageSpec, SectionSpec } from "@mdwrk/lander-content-contract";
2
+ import { LANDER_CORE_VERSION } from "./version.js";
3
+ import type { BreadcrumbItem, CompiledLanderSite, LanderDiagnostic, SitemapEntry } from "./types.js";
4
+ export { LANDER_CORE_VERSION };
5
+ export type * from "./types.js";
6
+ export * from "./cache/resource-policy.js";
7
+ export * from "./critical-css/profile.js";
8
+ export declare function defineLanderSite<T extends LanderSite>(site: T): T;
9
+ export declare function normalizeRouteSlug(value: string): string;
10
+ export declare function canonicalForSlug(productUrl: string, slug: string): string;
11
+ export declare function extractInternalLinks(value: string): string[];
12
+ export declare function textForSection(section: SectionSpec): string;
13
+ export declare function pageText(page: PageSpec): string;
14
+ export declare function wordCount(value: string): number;
15
+ export declare function buildBreadcrumbs(page: PageSpec): BreadcrumbItem[];
16
+ export declare function validateLanderSite(input: LanderSite): LanderDiagnostic[];
17
+ export declare function compileLanderSite(input: LanderSite): CompiledLanderSite;
18
+ export declare function buildSitemap(site: CompiledLanderSite): SitemapEntry[];
19
+ export declare function buildLlmsTxt(site: CompiledLanderSite): string;
20
+ export declare function buildRobotsTxt(site: CompiledLanderSite): string;
21
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAExF,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EACV,cAAc,EAEd,kBAAkB,EAGlB,gBAAgB,EAChB,YAAY,EACb,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,mBAAmB,EAAE,CAAC;AAC/B,mBAAmB,YAAY,CAAC;AAChC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,2BAA2B,CAAC;AAE1C,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,UAAU,EAAE,IAAI,EAAE,CAAC,GAAG,CAAC,CAEjE;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAW5D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,CAwB3D;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAS/C;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,GAAG,cAAc,EAAE,CAejE;AAoDD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,gBAAgB,EAAE,CA0CxE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,GAAG,kBAAkB,CAwBvE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,GAAG,YAAY,EAAE,CAIrE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAO7D;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB,GAAG,MAAM,CAc/D"}
package/dist/index.js ADDED
@@ -0,0 +1,231 @@
1
+ import { validateComponentIntent, validateStructuredDataIntent } from "@mdwrk/lander-content-contract";
2
+ import { LANDER_CORE_VERSION } from "./version.js";
3
+ export { LANDER_CORE_VERSION };
4
+ export * from "./cache/resource-policy.js";
5
+ export * from "./critical-css/profile.js";
6
+ export function defineLanderSite(site) {
7
+ return site;
8
+ }
9
+ export function normalizeRouteSlug(value) {
10
+ const trimmed = String(value ?? "").trim();
11
+ if (!trimmed || trimmed === "/")
12
+ return "/";
13
+ return `/${trimmed.replace(/^\/+|\/+$/g, "")}/`;
14
+ }
15
+ export function canonicalForSlug(productUrl, slug) {
16
+ const base = productUrl.replace(/\/+$/, "");
17
+ return `${base}${normalizeRouteSlug(slug)}`;
18
+ }
19
+ export function extractInternalLinks(value) {
20
+ const links = new Set();
21
+ const markdownLinks = String(value ?? "").matchAll(/\[[^\]]+]\((\/[^)\s]+)\)/g);
22
+ for (const match of markdownLinks) {
23
+ links.add(normalizeRouteSlug(match[1] ?? "/"));
24
+ }
25
+ const hrefLinks = String(value ?? "").matchAll(/\bhref=["'](\/[^"']+)["']/g);
26
+ for (const match of hrefLinks) {
27
+ links.add(normalizeRouteSlug(match[1] ?? "/"));
28
+ }
29
+ return [...links].sort();
30
+ }
31
+ export function textForSection(section) {
32
+ switch (section.kind) {
33
+ case "hero":
34
+ return [section.title, section.subtitle, section.eyebrow].filter(Boolean).join(" ");
35
+ case "feature_grid":
36
+ return [section.title, ...section.items.flatMap((item) => [item.title, item.description])].join(" ");
37
+ case "feature_detail":
38
+ return [section.title, section.body, ...(section.items ?? []).flatMap((item) => [item.title, item.description])].join(" ");
39
+ case "comparison":
40
+ return [section.title, ...section.columns.map((column) => column.label), ...section.rows.map((row) => row.label)].join(" ");
41
+ case "proof_matrix":
42
+ return [section.title, ...section.items.flatMap((item) => [item.claim, item.evidence])].join(" ");
43
+ case "package_grid":
44
+ return [section.title, ...section.packages.flatMap((item) => [item.name, item.description, item.install, ...(item.api ?? [])])].join(" ");
45
+ case "pricing":
46
+ case "cta":
47
+ return [section.title, section.body].filter(Boolean).join(" ");
48
+ case "faq":
49
+ return [section.title, ...section.items.flatMap((item) => [item.question, item.answer])].filter(Boolean).join(" ");
50
+ case "markdown":
51
+ return section.body;
52
+ default:
53
+ return assertNever(section);
54
+ }
55
+ }
56
+ export function pageText(page) {
57
+ return [
58
+ page.title,
59
+ page.description,
60
+ page.h1,
61
+ page.intro,
62
+ ...page.sections.map(textForSection),
63
+ ...(page.faq ?? []).flatMap((item) => [item.question, item.answer]),
64
+ ].join(" ");
65
+ }
66
+ export function wordCount(value) {
67
+ return (String(value ?? "").match(/\b[\w'-]+\b/g) ?? []).length;
68
+ }
69
+ export function buildBreadcrumbs(page) {
70
+ const slug = normalizeRouteSlug(page.slug);
71
+ if (slug === "/")
72
+ return [{ label: page.h1, href: "/" }];
73
+ const parts = slug.replace(/^\/|\/$/g, "").split("/");
74
+ const crumbs = [{ label: "Home", href: "/" }];
75
+ let cursor = "";
76
+ for (const part of parts) {
77
+ cursor += `/${part}`;
78
+ crumbs.push({
79
+ label: part.replace(/-/g, " ").replace(/\b\w/g, (value) => value.toUpperCase()),
80
+ href: normalizeRouteSlug(cursor),
81
+ });
82
+ }
83
+ crumbs[crumbs.length - 1] = { label: page.h1, href: slug };
84
+ return crumbs;
85
+ }
86
+ const slugifyIntentId = (value) => String(value ?? "")
87
+ .trim()
88
+ .toLowerCase()
89
+ .replace(/[^a-z0-9]+/g, "-")
90
+ .replace(/^-+|-+$/g, "") || "intent";
91
+ function compileComponentIntents(page, path) {
92
+ const sectionIntents = page.sections.map((section) => ({
93
+ id: `${path}#section-${slugifyIntentId(section.id)}`,
94
+ kind: section.kind,
95
+ sourceId: section.id,
96
+ data: section.title ? { title: section.title } : undefined,
97
+ pagePath: path,
98
+ source: "section",
99
+ }));
100
+ const schemaIntents = page.schema?.length
101
+ ? [{
102
+ id: `${path}#structured-data-graph`,
103
+ kind: "structured_data_graph",
104
+ sourceId: path,
105
+ data: { count: page.schema.length },
106
+ pagePath: path,
107
+ source: "schema",
108
+ }]
109
+ : [];
110
+ const declaredIntents = (page.componentIntents ?? []).map((intent) => ({
111
+ ...intent,
112
+ pagePath: path,
113
+ source: "declared",
114
+ }));
115
+ return [
116
+ { id: `${path}#page-shell`, kind: "page_shell", sourceId: path, pagePath: path, source: "page" },
117
+ { id: `${path}#breadcrumbs`, kind: "breadcrumbs", sourceId: path, pagePath: path, source: "page" },
118
+ ...sectionIntents,
119
+ ...schemaIntents,
120
+ ...declaredIntents,
121
+ ];
122
+ }
123
+ function compileSchemaIntents(page, path) {
124
+ return (page.schema ?? []).map((intent, index) => ({
125
+ ...intent,
126
+ id: intent.id ?? `${path}#schema-${index + 1}-${slugifyIntentId(intent.kind)}`,
127
+ pagePath: path,
128
+ source: "schema",
129
+ }));
130
+ }
131
+ export function validateLanderSite(input) {
132
+ const diagnostics = [];
133
+ const slugs = new Set();
134
+ if (!input.product?.name)
135
+ diagnostics.push({ level: "error", code: "product.name.missing", message: "Product name is required." });
136
+ if (!input.product?.canonicalUrl)
137
+ diagnostics.push({ level: "error", code: "product.canonicalUrl.missing", message: "Product canonicalUrl is required." });
138
+ if (!Array.isArray(input.pages) || input.pages.length === 0) {
139
+ diagnostics.push({ level: "error", code: "pages.empty", message: "At least one page is required." });
140
+ }
141
+ for (const page of input.pages ?? []) {
142
+ const slug = normalizeRouteSlug(page.slug);
143
+ if (slugs.has(slug))
144
+ diagnostics.push({ level: "error", code: "page.slug.duplicate", message: `Duplicate page slug ${slug}.`, slug });
145
+ slugs.add(slug);
146
+ for (const field of ["title", "description", "h1", "intro"]) {
147
+ if (!page[field]?.trim())
148
+ diagnostics.push({ level: "error", code: `page.${field}.missing`, message: `Page ${slug} is missing ${field}.`, slug });
149
+ }
150
+ if (!page.sections?.length)
151
+ diagnostics.push({ level: "error", code: "page.sections.empty", message: `Page ${slug} must have at least one section.`, slug });
152
+ if (wordCount(page.intro) < 8)
153
+ diagnostics.push({ level: "warning", code: "page.intro.thin", message: `Page ${slug} has a thin crawlable intro.`, slug });
154
+ if (page.kind === "feature" && !page.faq?.length && !page.sections.some((section) => section.kind === "faq")) {
155
+ diagnostics.push({ level: "warning", code: "page.feature.faq.missing", message: `Feature page ${slug} should include FAQ content.`, slug });
156
+ }
157
+ if (page.kind === "compare" && !page.sections.some((section) => section.kind === "comparison")) {
158
+ diagnostics.push({ level: "error", code: "page.compare.matrix.missing", message: `Comparison page ${slug} must include a comparison section.`, slug });
159
+ }
160
+ if (page.kind === "package" && !page.sections.some((section) => section.kind === "package_grid" || section.kind === "markdown")) {
161
+ diagnostics.push({ level: "warning", code: "page.package.install.missing", message: `Package page ${slug} should include install or API content.`, slug });
162
+ }
163
+ page.schema?.forEach((schema, index) => {
164
+ for (const failure of validateStructuredDataIntent({ id: schema.id, kind: schema.kind, data: schema.data })) {
165
+ diagnostics.push({ level: "error", code: "page.schema.intent.invalid", message: `Schema intent ${index + 1} on ${slug}: ${failure}.`, slug });
166
+ }
167
+ });
168
+ page.componentIntents?.forEach((intent) => {
169
+ for (const failure of validateComponentIntent(intent)) {
170
+ diagnostics.push({ level: "error", code: "page.component.intent.invalid", message: `Component intent ${intent.id || "<missing>"} on ${slug}: ${failure}.`, slug });
171
+ }
172
+ });
173
+ }
174
+ return diagnostics;
175
+ }
176
+ export function compileLanderSite(input) {
177
+ const diagnostics = validateLanderSite(input);
178
+ const pages = input.pages.map((page) => {
179
+ const path = normalizeRouteSlug(page.slug);
180
+ const text = pageText(page);
181
+ return {
182
+ ...page,
183
+ slug: path,
184
+ path,
185
+ canonicalUrl: page.seo?.canonical ?? canonicalForSlug(input.product.canonicalUrl, path),
186
+ breadcrumbs: buildBreadcrumbs(page),
187
+ internalLinks: extractInternalLinks(text),
188
+ wordCount: wordCount(text),
189
+ componentIntents: compileComponentIntents(page, path),
190
+ schemaIntents: compileSchemaIntents(page, path),
191
+ };
192
+ });
193
+ return {
194
+ ...input,
195
+ pages,
196
+ pageByPath: new Map(pages.map((page) => [page.path, page])),
197
+ diagnostics,
198
+ };
199
+ }
200
+ export function buildSitemap(site) {
201
+ return site.pages
202
+ .filter((page) => page.seo?.noindex !== true)
203
+ .map((page) => ({ loc: page.canonicalUrl }));
204
+ }
205
+ export function buildLlmsTxt(site) {
206
+ const title = site.ai?.llmsTxtTitle ?? site.product.name;
207
+ const facts = site.ai?.coreFacts?.map((fact) => `- ${fact}`) ?? [`- ${site.product.description}`];
208
+ const pages = site.pages
209
+ .filter((page) => page.seo?.noindex !== true)
210
+ .map((page) => `- [${page.h1}](${page.canonicalUrl}) - ${page.description}`);
211
+ return [`# ${title}`, "", ...facts, "", "## Pages", ...pages, ""].join("\n");
212
+ }
213
+ export function buildRobotsTxt(site) {
214
+ return [
215
+ "User-agent: *",
216
+ "Allow: /",
217
+ "",
218
+ "User-agent: OAI-SearchBot",
219
+ "Allow: /",
220
+ "",
221
+ "User-agent: GPTBot",
222
+ "Disallow: /",
223
+ "",
224
+ `Sitemap: ${site.product.canonicalUrl.replace(/\/+$/, "")}/sitemap.xml`,
225
+ "",
226
+ ].join("\n");
227
+ }
228
+ function assertNever(value) {
229
+ throw new Error(`Unsupported section: ${JSON.stringify(value)}`);
230
+ }
231
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import type { ComponentIntentSpec, LanderSite, PageSpec, StructuredDataIntentSpec } from "@mdwrk/lander-content-contract";
2
+ export interface LanderDiagnostic {
3
+ level: "error" | "warning";
4
+ code: string;
5
+ message: string;
6
+ slug?: string;
7
+ }
8
+ export interface BreadcrumbItem {
9
+ label: string;
10
+ href: string;
11
+ }
12
+ export interface CompiledComponentIntent extends ComponentIntentSpec {
13
+ pagePath: string;
14
+ source: "declared" | "page" | "schema" | "section";
15
+ }
16
+ export interface CompiledStructuredDataIntent extends StructuredDataIntentSpec {
17
+ id: string;
18
+ pagePath: string;
19
+ source: "declared" | "schema";
20
+ }
21
+ export interface CompiledPage extends PageSpec {
22
+ path: string;
23
+ canonicalUrl: string;
24
+ breadcrumbs: BreadcrumbItem[];
25
+ internalLinks: string[];
26
+ wordCount: number;
27
+ componentIntents: CompiledComponentIntent[];
28
+ schemaIntents: CompiledStructuredDataIntent[];
29
+ }
30
+ export interface CompiledLanderSite extends Omit<LanderSite, "pages"> {
31
+ pages: CompiledPage[];
32
+ pageByPath: Map<string, CompiledPage>;
33
+ diagnostics: LanderDiagnostic[];
34
+ }
35
+ export interface SitemapEntry {
36
+ loc: string;
37
+ lastmod?: string;
38
+ }
39
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,UAAU,EAAE,QAAQ,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AAE1H,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,GAAG,SAAS,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,uBAAwB,SAAQ,mBAAmB;IAClE,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;CACpD;AAED,MAAM,WAAW,4BAA6B,SAAQ,wBAAwB;IAC5E,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,UAAU,GAAG,QAAQ,CAAC;CAC/B;AAED,MAAM,WAAW,YAAa,SAAQ,QAAQ;IAC5C,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,uBAAuB,EAAE,CAAC;IAC5C,aAAa,EAAE,4BAA4B,EAAE,CAAC;CAC/C;AAED,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC;IACnE,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC;IACtC,WAAW,EAAE,gBAAgB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,YAAY;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export declare const LANDER_CORE_VERSION = "0.1.1";
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,UAAU,CAAC"}
@@ -0,0 +1,2 @@
1
+ export const LANDER_CORE_VERSION = "0.1.1";
2
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,mBAAmB,GAAG,OAAO,CAAC"}
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@mdwrk/lander-core",
3
+ "version": "0.1.2",
4
+ "type": "module",
5
+ "license": "Apache-2.0",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "engines": {
10
+ "node": ">=20 <23"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md"
15
+ ],
16
+ "sideEffects": false,
17
+ "keywords": [
18
+ "lander",
19
+ "compiler",
20
+ "seo",
21
+ "portable"
22
+ ],
23
+ "description": "Portable compiler and diagnostics for product landing site content.",
24
+ "dependencies": {
25
+ "@mdwrk/lander-content-contract": "^0.1.2"
26
+ },
27
+ "scripts": {
28
+ "build": "npm run build -w @mdwrk/lander-content-contract && tsc -p tsconfig.json",
29
+ "typecheck": "npm run build -w @mdwrk/lander-content-contract && tsc --noEmit -p tsconfig.json",
30
+ "lint": "npm run typecheck",
31
+ "test": "npm run build && node ./tests/run-smoke.mjs",
32
+ "prepack": "npm run build"
33
+ },
34
+ "exports": {
35
+ ".": {
36
+ "types": "./dist/index.d.ts",
37
+ "import": "./dist/index.js"
38
+ },
39
+ "./version": {
40
+ "types": "./dist/version.d.ts",
41
+ "import": "./dist/version.js"
42
+ },
43
+ "./types": {
44
+ "types": "./dist/types.d.ts",
45
+ "import": "./dist/types.js"
46
+ }
47
+ },
48
+ "main": "./dist/index.js",
49
+ "types": "./dist/index.d.ts",
50
+ "author": "Jacob Stewart <jacob@swarmauri.com>",
51
+ "homepage": "https://mdwrk.com"
52
+ }