@ingram-tech/nk-blog 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +78 -0
- package/dist/blog.d.ts +54 -0
- package/dist/blog.d.ts.map +1 -0
- package/dist/blog.js +99 -0
- package/dist/blog.js.map +1 -0
- package/dist/contract.d.ts +102 -0
- package/dist/contract.d.ts.map +1 -0
- package/dist/contract.js +57 -0
- package/dist/contract.js.map +1 -0
- package/dist/date.d.ts +6 -0
- package/dist/date.d.ts.map +1 -0
- package/dist/date.js +12 -0
- package/dist/date.js.map +1 -0
- package/dist/http.d.ts +3 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +10 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/keys.d.ts +8 -0
- package/dist/keys.d.ts.map +1 -0
- package/dist/keys.js +11 -0
- package/dist/keys.js.map +1 -0
- package/dist/limited-mdx.d.ts +33 -0
- package/dist/limited-mdx.d.ts.map +1 -0
- package/dist/limited-mdx.js +151 -0
- package/dist/limited-mdx.js.map +1 -0
- package/dist/reading-time.d.ts +10 -0
- package/dist/reading-time.d.ts.map +1 -0
- package/dist/reading-time.js +11 -0
- package/dist/reading-time.js.map +1 -0
- package/dist/render.d.ts +38 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +39 -0
- package/dist/render.js.map +1 -0
- package/dist/rss.d.ts +18 -0
- package/dist/rss.d.ts.map +1 -0
- package/dist/rss.js +49 -0
- package/dist/rss.js.map +1 -0
- package/dist/schema.d.ts +61 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/schema.js +48 -0
- package/dist/schema.js.map +1 -0
- package/dist/seo.d.ts +18 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +30 -0
- package/dist/seo.js.map +1 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +10 -0
- package/dist/server.js.map +1 -0
- package/dist/sources/fs.d.ts +12 -0
- package/dist/sources/fs.d.ts.map +1 -0
- package/dist/sources/fs.js +54 -0
- package/dist/sources/fs.js.map +1 -0
- package/dist/sources/github.d.ts +20 -0
- package/dist/sources/github.d.ts.map +1 -0
- package/dist/sources/github.js +62 -0
- package/dist/sources/github.js.map +1 -0
- package/dist/sources/publish.d.ts +36 -0
- package/dist/sources/publish.d.ts.map +1 -0
- package/dist/sources/publish.js +55 -0
- package/dist/sources/publish.js.map +1 -0
- package/dist/types.d.ts +33 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/unstyled/callout.d.ts +8 -0
- package/dist/unstyled/callout.d.ts.map +1 -0
- package/dist/unstyled/callout.js +8 -0
- package/dist/unstyled/callout.js.map +1 -0
- package/dist/unstyled/figure.d.ts +8 -0
- package/dist/unstyled/figure.d.ts.map +1 -0
- package/dist/unstyled/figure.js +9 -0
- package/dist/unstyled/figure.js.map +1 -0
- package/dist/unstyled/index.d.ts +16 -0
- package/dist/unstyled/index.d.ts.map +1 -0
- package/dist/unstyled/index.js +23 -0
- package/dist/unstyled/index.js.map +1 -0
- package/dist/unstyled/newsletter-subscribe.d.ts +7 -0
- package/dist/unstyled/newsletter-subscribe.d.ts.map +1 -0
- package/dist/unstyled/newsletter-subscribe.js +7 -0
- package/dist/unstyled/newsletter-subscribe.js.map +1 -0
- package/dist/unstyled/tweet.d.ts +8 -0
- package/dist/unstyled/tweet.d.ts.map +1 -0
- package/dist/unstyled/tweet.js +8 -0
- package/dist/unstyled/tweet.js.map +1 -0
- package/dist/unstyled/youtube.d.ts +7 -0
- package/dist/unstyled/youtube.d.ts.map +1 -0
- package/dist/unstyled/youtube.js +11 -0
- package/dist/unstyled/youtube.js.map +1 -0
- package/package.json +78 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import matter from "gray-matter";
|
|
2
|
+
import { visit } from "unist-util-visit";
|
|
3
|
+
import { toErrorMessage } from "./http.js";
|
|
4
|
+
// Lowercase JSX that isn't one of these is almost certainly a miscased
|
|
5
|
+
// vocabulary reference (<callout>), which MDX would render as an inert,
|
|
6
|
+
// invisible HTML element — the one silent failure mode, so reject it.
|
|
7
|
+
const HTML_TAGS = new Set([
|
|
8
|
+
"a",
|
|
9
|
+
"abbr",
|
|
10
|
+
"aside",
|
|
11
|
+
"b",
|
|
12
|
+
"blockquote",
|
|
13
|
+
"br",
|
|
14
|
+
"caption",
|
|
15
|
+
"cite",
|
|
16
|
+
"code",
|
|
17
|
+
"dd",
|
|
18
|
+
"del",
|
|
19
|
+
"details",
|
|
20
|
+
"div",
|
|
21
|
+
"dl",
|
|
22
|
+
"dt",
|
|
23
|
+
"em",
|
|
24
|
+
"figcaption",
|
|
25
|
+
"figure",
|
|
26
|
+
"h1",
|
|
27
|
+
"h2",
|
|
28
|
+
"h3",
|
|
29
|
+
"h4",
|
|
30
|
+
"h5",
|
|
31
|
+
"h6",
|
|
32
|
+
"hr",
|
|
33
|
+
"i",
|
|
34
|
+
"img",
|
|
35
|
+
"ins",
|
|
36
|
+
"kbd",
|
|
37
|
+
"li",
|
|
38
|
+
"main",
|
|
39
|
+
"mark",
|
|
40
|
+
"nav",
|
|
41
|
+
"ol",
|
|
42
|
+
"p",
|
|
43
|
+
"pre",
|
|
44
|
+
"q",
|
|
45
|
+
"s",
|
|
46
|
+
"samp",
|
|
47
|
+
"section",
|
|
48
|
+
"small",
|
|
49
|
+
"span",
|
|
50
|
+
"strong",
|
|
51
|
+
"sub",
|
|
52
|
+
"summary",
|
|
53
|
+
"sup",
|
|
54
|
+
"table",
|
|
55
|
+
"tbody",
|
|
56
|
+
"td",
|
|
57
|
+
"tfoot",
|
|
58
|
+
"th",
|
|
59
|
+
"thead",
|
|
60
|
+
"tr",
|
|
61
|
+
"u",
|
|
62
|
+
"ul",
|
|
63
|
+
"var",
|
|
64
|
+
"video",
|
|
65
|
+
"wbr",
|
|
66
|
+
]);
|
|
67
|
+
function checkElement(node, allow, file) {
|
|
68
|
+
const name = node.name;
|
|
69
|
+
// Nameless = fragment (<>…</>), which is inert prose grouping.
|
|
70
|
+
if (name === null)
|
|
71
|
+
return;
|
|
72
|
+
if (/^[a-z]/.test(name)) {
|
|
73
|
+
if (!HTML_TAGS.has(name)) {
|
|
74
|
+
file.fail(`Unknown HTML tag <${name}> — a miscased component? Vocabulary names are capitalized.`, node);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (!allow.includes(name)) {
|
|
78
|
+
file.fail(`<${name}> is not in this blog's component vocabulary (${allow.join(", ")}).`, node);
|
|
79
|
+
}
|
|
80
|
+
for (const attribute of node.attributes) {
|
|
81
|
+
if (attribute.type !== "mdxJsxAttribute") {
|
|
82
|
+
file.fail(`<${name}> uses a spread attribute; only literal props are allowed.`, node);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const value = attribute.value;
|
|
86
|
+
// null/undefined = bare boolean prop; string = literal. Anything else is
|
|
87
|
+
// an attribute-value expression — executable, so allow only simple
|
|
88
|
+
// literals.
|
|
89
|
+
if (value === null || value === undefined || typeof value === "string") {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const statement = value.data?.estree?.body[0];
|
|
93
|
+
const expression = statement?.type === "ExpressionStatement"
|
|
94
|
+
? statement.expression
|
|
95
|
+
: undefined;
|
|
96
|
+
if (expression?.type !== "Literal") {
|
|
97
|
+
file.fail(`<${name} ${attribute.name}={…}> — attribute expressions are not allowed in limited MDX; use a literal.`, node);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/** Remark plugin implementing the boundary. Throws (via `file.fail`) on violation. */
|
|
102
|
+
export function remarkLimitedMdx(options) {
|
|
103
|
+
return (tree, file) => {
|
|
104
|
+
visit(tree, (node) => {
|
|
105
|
+
switch (node.type) {
|
|
106
|
+
case "mdxjsEsm":
|
|
107
|
+
file.fail("import/export statements are not allowed in limited MDX.", node);
|
|
108
|
+
break;
|
|
109
|
+
case "mdxFlowExpression":
|
|
110
|
+
case "mdxTextExpression":
|
|
111
|
+
file.fail("{…} expressions are not allowed in limited MDX.", node);
|
|
112
|
+
break;
|
|
113
|
+
case "mdxJsxFlowElement":
|
|
114
|
+
case "mdxJsxTextElement":
|
|
115
|
+
checkElement(node, options.allow, file);
|
|
116
|
+
break;
|
|
117
|
+
default:
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Admin's pre-publish lint: compile (without evaluating) a post source —
|
|
125
|
+
* frontmatter included — against a vocabulary. Also surfaces MDX syntax
|
|
126
|
+
* errors, so an LLM-authored post can never break a target site's build.
|
|
127
|
+
*/
|
|
128
|
+
export async function validateLimitedMdx(source, options) {
|
|
129
|
+
const { compile } = await import("@mdx-js/mdx");
|
|
130
|
+
const { default: remarkGfm } = await import("remark-gfm");
|
|
131
|
+
const { content } = matter(source);
|
|
132
|
+
try {
|
|
133
|
+
await compile(content, {
|
|
134
|
+
remarkPlugins: [remarkGfm, [remarkLimitedMdx, options]],
|
|
135
|
+
});
|
|
136
|
+
return { ok: true, errors: [] };
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
const place = error !== null &&
|
|
140
|
+
typeof error === "object" &&
|
|
141
|
+
"line" in error &&
|
|
142
|
+
"column" in error
|
|
143
|
+
? {
|
|
144
|
+
line: typeof error.line === "number" ? error.line : undefined,
|
|
145
|
+
column: typeof error.column === "number" ? error.column : undefined,
|
|
146
|
+
}
|
|
147
|
+
: {};
|
|
148
|
+
return { ok: false, errors: [{ message: toErrorMessage(error), ...place }] };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=limited-mdx.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"limited-mdx.js","sourceRoot":"","sources":["../src/limited-mdx.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const DEFAULT_WORDS_PER_MINUTE = 220;
|
|
2
|
+
export interface ReadingTime {
|
|
3
|
+
words: number;
|
|
4
|
+
minutes: number;
|
|
5
|
+
/** e.g. "5 min read". */
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
/** Real reading time from the body — replaces the fleet's faked "5 min read". */
|
|
9
|
+
export declare function readingTime(content: string, wordsPerMinute?: number): ReadingTime;
|
|
10
|
+
//# sourceMappingURL=reading-time.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reading-time.d.ts","sourceRoot":"","sources":["../src/reading-time.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,iFAAiF;AACjF,wBAAgB,WAAW,CAC1B,OAAO,EAAE,MAAM,EACf,cAAc,GAAE,MAAiC,GAC/C,WAAW,CAOb"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const DEFAULT_WORDS_PER_MINUTE = 220;
|
|
2
|
+
/** Real reading time from the body — replaces the fleet's faked "5 min read". */
|
|
3
|
+
export function readingTime(content, wordsPerMinute = DEFAULT_WORDS_PER_MINUTE) {
|
|
4
|
+
const words = content
|
|
5
|
+
.trim()
|
|
6
|
+
.split(/\s+/)
|
|
7
|
+
.filter((token) => token.length > 0).length;
|
|
8
|
+
const minutes = Math.max(1, Math.ceil(words / wordsPerMinute));
|
|
9
|
+
return { words, minutes, text: `${minutes} min read` };
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=reading-time.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reading-time.js","sourceRoot":"","sources":["../src/reading-time.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,wBAAwB,GAAG,GAAG,CAAC;AAS5C,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAC1B,OAAe,EACf,iBAAyB,wBAAwB;IAEjD,MAAM,KAAK,GAAG,OAAO;SACnB,IAAI,EAAE;SACN,KAAK,CAAC,KAAK,CAAC;SACZ,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC;IAC7C,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,cAAc,CAAC,CAAC,CAAC;IAC/D,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,OAAO,WAAW,EAAE,CAAC;AACxD,CAAC"}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { ComponentType, ReactElement } from "react";
|
|
2
|
+
import { type Components } from "react-markdown";
|
|
3
|
+
import type { BlogComponents } from "./contract.js";
|
|
4
|
+
import type { BlogPost } from "./types.js";
|
|
5
|
+
export { remarkLimitedMdx, validateLimitedMdx } from "./limited-mdx.js";
|
|
6
|
+
export type { LimitedMdxOptions, LimitedMdxResult, LimitedMdxViolation, } from "./limited-mdx.js";
|
|
7
|
+
/**
|
|
8
|
+
* The element map a site passes to style prose (h1–h4, p, code, tables, …).
|
|
9
|
+
* One key set shared by both pipelines — react-markdown's `Components` is the
|
|
10
|
+
* canonical shape. The map itself never ships from nk-blog: it is the brand.
|
|
11
|
+
*/
|
|
12
|
+
export type BlogElements = Components;
|
|
13
|
+
export type BespokeComponents = Record<string, ComponentType<never>>;
|
|
14
|
+
/** Renders a `.md` body. Pure data — the automated publisher's format. */
|
|
15
|
+
export declare const MarkdownBody: React.FC<{
|
|
16
|
+
content: string;
|
|
17
|
+
elements?: BlogElements;
|
|
18
|
+
}>;
|
|
19
|
+
/**
|
|
20
|
+
* Renders an `.mdx` body (async server component). `limited` (default true)
|
|
21
|
+
* enforces the vocabulary-only trust boundary; Tier-2 posts pass `bespoke`
|
|
22
|
+
* components, which turns enforcement off — those are human-reviewed code.
|
|
23
|
+
*/
|
|
24
|
+
export declare function MdxBody(props: {
|
|
25
|
+
content: string;
|
|
26
|
+
components: BlogComponents;
|
|
27
|
+
elements?: BlogElements;
|
|
28
|
+
bespoke?: BespokeComponents;
|
|
29
|
+
limited?: boolean;
|
|
30
|
+
}): Promise<ReactElement>;
|
|
31
|
+
/** Format-dispatching renderer: the one component a post page needs. */
|
|
32
|
+
export declare function PostBody(props: {
|
|
33
|
+
post: BlogPost;
|
|
34
|
+
components: BlogComponents;
|
|
35
|
+
elements?: BlogElements;
|
|
36
|
+
bespoke?: BespokeComponents;
|
|
37
|
+
}): Promise<ReactElement>;
|
|
38
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAEzD,OAAiB,EAAE,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAG3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACxE,YAAY,EACX,iBAAiB,EACjB,gBAAgB,EAChB,mBAAmB,GACnB,MAAM,kBAAkB,CAAC;AAE1B;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC;AAGtC,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;AAErE,0EAA0E;AAC1E,eAAO,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,CAAC;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,YAAY,CAAC;CACxB,CAIA,CAAC;AAEF;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,KAAK,EAAE;IACpC,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB,GAAG,OAAO,CAAC,YAAY,CAAC,CAWxB;AAED,wEAAwE;AACxE,wBAAsB,QAAQ,CAAC,KAAK,EAAE;IACrC,IAAI,EAAE,QAAQ,CAAC;IACf,UAAU,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,YAAY,CAAC;IACxB,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC5B,GAAG,OAAO,CAAC,YAAY,CAAC,CAUxB"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { evaluate } from "@mdx-js/mdx";
|
|
3
|
+
import * as runtime from "react/jsx-runtime";
|
|
4
|
+
import Markdown from "react-markdown";
|
|
5
|
+
import remarkGfm from "remark-gfm";
|
|
6
|
+
import { remarkLimitedMdx } from "./limited-mdx.js";
|
|
7
|
+
export { remarkLimitedMdx, validateLimitedMdx } from "./limited-mdx.js";
|
|
8
|
+
/** Renders a `.md` body. Pure data — the automated publisher's format. */
|
|
9
|
+
export const MarkdownBody = ({ content, elements }) => (_jsx(Markdown, { remarkPlugins: [remarkGfm], components: elements, children: content }));
|
|
10
|
+
/**
|
|
11
|
+
* Renders an `.mdx` body (async server component). `limited` (default true)
|
|
12
|
+
* enforces the vocabulary-only trust boundary; Tier-2 posts pass `bespoke`
|
|
13
|
+
* components, which turns enforcement off — those are human-reviewed code.
|
|
14
|
+
*/
|
|
15
|
+
export async function MdxBody(props) {
|
|
16
|
+
const limited = props.limited ?? props.bespoke === undefined;
|
|
17
|
+
const scope = { ...props.components, ...props.bespoke };
|
|
18
|
+
const remarkPlugins = limited
|
|
19
|
+
? [remarkGfm, [remarkLimitedMdx, { allow: Object.keys(scope) }]]
|
|
20
|
+
: [remarkGfm];
|
|
21
|
+
const { default: MDXContent } = await evaluate(props.content, {
|
|
22
|
+
...runtime,
|
|
23
|
+
remarkPlugins,
|
|
24
|
+
});
|
|
25
|
+
return _jsx(MDXContent, { components: { ...props.elements, ...scope } });
|
|
26
|
+
}
|
|
27
|
+
/** Format-dispatching renderer: the one component a post page needs. */
|
|
28
|
+
export async function PostBody(props) {
|
|
29
|
+
if (props.post.format === "md") {
|
|
30
|
+
return _jsx(MarkdownBody, { content: props.post.content, elements: props.elements });
|
|
31
|
+
}
|
|
32
|
+
return MdxBody({
|
|
33
|
+
content: props.post.content,
|
|
34
|
+
components: props.components,
|
|
35
|
+
elements: props.elements,
|
|
36
|
+
bespoke: props.bespoke,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,OAAO,KAAK,OAAO,MAAM,mBAAmB,CAAC;AAC7C,OAAO,QAA6B,MAAM,gBAAgB,CAAC;AAC3D,OAAO,SAAS,MAAM,YAAY,CAAC;AAGnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAiBxE,0EAA0E;AAC1E,MAAM,CAAC,MAAM,YAAY,GAGpB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAC/B,KAAC,QAAQ,IAAC,aAAa,EAAE,CAAC,SAAS,CAAC,EAAE,UAAU,EAAE,QAAQ,YACxD,OAAO,GACE,CACX,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,KAM7B;IACA,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC;IAC7D,MAAM,KAAK,GAAG,EAAE,GAAG,KAAK,CAAC,UAAU,EAAE,GAAG,KAAK,CAAC,OAAO,EAAE,CAAC;IACxD,MAAM,aAAa,GAAkB,OAAO;QAC3C,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,gBAAgB,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACf,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE;QAC7D,GAAG,OAAO;QACV,aAAa;KACb,CAAC,CAAC;IACH,OAAO,KAAC,UAAU,IAAC,UAAU,EAAE,EAAE,GAAG,KAAK,CAAC,QAAQ,EAAE,GAAG,KAAK,EAAE,GAAI,CAAC;AACpE,CAAC;AAED,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,KAK9B;IACA,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,CAAC;QAChC,OAAO,KAAC,YAAY,IAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,GAAI,CAAC;IAChF,CAAC;IACD,OAAO,OAAO,CAAC;QACd,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,OAAO;QAC3B,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO,EAAE,KAAK,CAAC,OAAO;KACtB,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/rss.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { BlogPostPreview } from "./types.js";
|
|
2
|
+
export interface RssConfig {
|
|
3
|
+
title: string;
|
|
4
|
+
description?: string;
|
|
5
|
+
/** Absolute site origin, e.g. "https://example.com". */
|
|
6
|
+
siteUrl: string;
|
|
7
|
+
/** Path prefix of the blog, e.g. "/posts". */
|
|
8
|
+
basePath: string;
|
|
9
|
+
/** Absolute URL the feed is served from, e.g. `${siteUrl}/rss.xml`. */
|
|
10
|
+
feedUrl: string;
|
|
11
|
+
language?: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* RSS 2.0 feed for a post list. Serve from a route handler (`rss.xml/route.ts`
|
|
15
|
+
* with `export const dynamic = "force-static"`) so it renders at build.
|
|
16
|
+
*/
|
|
17
|
+
export declare function generateRss(config: RssConfig, posts: BlogPostPreview[]): string;
|
|
18
|
+
//# sourceMappingURL=rss.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rss.d.ts","sourceRoot":"","sources":["../src/rss.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,uEAAuE;IACvE,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AASD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,MAAM,CAyC/E"}
|
package/dist/rss.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const escapeXml = (value) => value
|
|
2
|
+
.replaceAll("&", "&")
|
|
3
|
+
.replaceAll("<", "<")
|
|
4
|
+
.replaceAll(">", ">")
|
|
5
|
+
.replaceAll('"', """);
|
|
6
|
+
/**
|
|
7
|
+
* RSS 2.0 feed for a post list. Serve from a route handler (`rss.xml/route.ts`
|
|
8
|
+
* with `export const dynamic = "force-static"`) so it renders at build.
|
|
9
|
+
*/
|
|
10
|
+
export function generateRss(config, posts) {
|
|
11
|
+
const items = posts
|
|
12
|
+
.map((post) => {
|
|
13
|
+
const url = `${config.siteUrl}${config.basePath}/${post.slug}`;
|
|
14
|
+
return [
|
|
15
|
+
"\t\t<item>",
|
|
16
|
+
`\t\t\t<title>${escapeXml(post.title)}</title>`,
|
|
17
|
+
`\t\t\t<link>${escapeXml(url)}</link>`,
|
|
18
|
+
`\t\t\t<guid isPermaLink="true">${escapeXml(url)}</guid>`,
|
|
19
|
+
`\t\t\t<pubDate>${new Date(post.date).toUTCString()}</pubDate>`,
|
|
20
|
+
`\t\t\t<description>${escapeXml(post.description)}</description>`,
|
|
21
|
+
...post.authors.map((author) => `\t\t\t<dc:creator>${escapeXml(author)}</dc:creator>`),
|
|
22
|
+
...(post.category
|
|
23
|
+
? [`\t\t\t<category>${escapeXml(post.category)}</category>`]
|
|
24
|
+
: []),
|
|
25
|
+
"\t\t</item>",
|
|
26
|
+
].join("\n");
|
|
27
|
+
})
|
|
28
|
+
.join("\n");
|
|
29
|
+
return [
|
|
30
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
31
|
+
'<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">',
|
|
32
|
+
"\t<channel>",
|
|
33
|
+
`\t\t<title>${escapeXml(config.title)}</title>`,
|
|
34
|
+
`\t\t<link>${escapeXml(config.siteUrl + config.basePath)}</link>`,
|
|
35
|
+
`\t\t<description>${escapeXml(config.description ?? config.title)}</description>`,
|
|
36
|
+
`\t\t<language>${escapeXml(config.language ?? "en")}</language>`,
|
|
37
|
+
`\t\t<atom:link href="${escapeXml(config.feedUrl)}" rel="self" type="application/rss+xml"/>`,
|
|
38
|
+
...(posts[0]
|
|
39
|
+
? [
|
|
40
|
+
`\t\t<lastBuildDate>${new Date(posts[0].date).toUTCString()}</lastBuildDate>`,
|
|
41
|
+
]
|
|
42
|
+
: []),
|
|
43
|
+
items,
|
|
44
|
+
"\t</channel>",
|
|
45
|
+
"</rss>",
|
|
46
|
+
"",
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=rss.js.map
|
package/dist/rss.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rss.js","sourceRoot":"","sources":["../src/rss.ts"],"names":[],"mappings":"AAcA,MAAM,SAAS,GAAG,CAAC,KAAa,EAAU,EAAE,CAC3C,KAAK;KACH,UAAU,CAAC,GAAG,EAAE,OAAO,CAAC;KACxB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;KACvB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC;KACvB,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAE7B;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,MAAiB,EAAE,KAAwB;IACtE,MAAM,KAAK,GAAG,KAAK;SACjB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QACb,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;QAC/D,OAAO;YACN,YAAY;YACZ,gBAAgB,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU;YAC/C,eAAe,SAAS,CAAC,GAAG,CAAC,SAAS;YACtC,kCAAkC,SAAS,CAAC,GAAG,CAAC,SAAS;YACzD,kBAAkB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,YAAY;YAC/D,sBAAsB,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,gBAAgB;YACjE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAClB,CAAC,MAAM,EAAE,EAAE,CAAC,qBAAqB,SAAS,CAAC,MAAM,CAAC,eAAe,CACjE;YACD,GAAG,CAAC,IAAI,CAAC,QAAQ;gBAChB,CAAC,CAAC,CAAC,mBAAmB,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;gBAC5D,CAAC,CAAC,EAAE,CAAC;YACN,aAAa;SACb,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACd,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,OAAO;QACN,wCAAwC;QACxC,0GAA0G;QAC1G,aAAa;QACb,cAAc,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,UAAU;QAC/C,aAAa,SAAS,CAAC,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS;QACjE,oBAAoB,SAAS,CAAC,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,KAAK,CAAC,gBAAgB;QACjF,iBAAiB,SAAS,CAAC,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,aAAa;QAChE,wBAAwB,SAAS,CAAC,MAAM,CAAC,OAAO,CAAC,2CAA2C;QAC5F,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;YACX,CAAC,CAAC;gBACA,sBAAsB,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,kBAAkB;aAC7E;YACF,CAAC,CAAC,EAAE,CAAC;QACN,KAAK;QACL,cAAc;QACd,QAAQ;QACR,EAAE;KACF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACd,CAAC"}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* The shared frontmatter contract. Every post on every site validates against
|
|
4
|
+
* this; unknown keys are preserved-ignored (sites may carry extras locally).
|
|
5
|
+
*
|
|
6
|
+
* Normalizations: `coverImage` is accepted as an alias of `image`; `author`
|
|
7
|
+
* (string or list) and `authors` collapse into a single `authors: string[]`.
|
|
8
|
+
*/
|
|
9
|
+
export declare const blogFrontmatterSchema: z.ZodPipe<z.ZodObject<{
|
|
10
|
+
title: z.ZodString;
|
|
11
|
+
seoTitle: z.ZodOptional<z.ZodString>;
|
|
12
|
+
description: z.ZodString;
|
|
13
|
+
date: z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodDate]>, z.ZodTransform<string, string | Date>>;
|
|
14
|
+
updated: z.ZodOptional<z.ZodPipe<z.ZodUnion<readonly [z.ZodString, z.ZodDate]>, z.ZodTransform<string, string | Date>>>;
|
|
15
|
+
author: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
16
|
+
authors: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
17
|
+
category: z.ZodOptional<z.ZodString>;
|
|
18
|
+
tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
19
|
+
image: z.ZodOptional<z.ZodString>;
|
|
20
|
+
coverImage: z.ZodOptional<z.ZodString>;
|
|
21
|
+
slug: z.ZodOptional<z.ZodString>;
|
|
22
|
+
draft: z.ZodDefault<z.ZodBoolean>;
|
|
23
|
+
featured: z.ZodDefault<z.ZodBoolean>;
|
|
24
|
+
lang: z.ZodOptional<z.ZodString>;
|
|
25
|
+
canonical: z.ZodOptional<z.ZodString>;
|
|
26
|
+
}, z.core.$strip>, z.ZodTransform<{
|
|
27
|
+
image: string | undefined;
|
|
28
|
+
authors: string[];
|
|
29
|
+
title: string;
|
|
30
|
+
description: string;
|
|
31
|
+
date: string;
|
|
32
|
+
tags: string[];
|
|
33
|
+
draft: boolean;
|
|
34
|
+
featured: boolean;
|
|
35
|
+
seoTitle?: string | undefined;
|
|
36
|
+
updated?: string | undefined;
|
|
37
|
+
category?: string | undefined;
|
|
38
|
+
slug?: string | undefined;
|
|
39
|
+
lang?: string | undefined;
|
|
40
|
+
canonical?: string | undefined;
|
|
41
|
+
}, {
|
|
42
|
+
title: string;
|
|
43
|
+
description: string;
|
|
44
|
+
date: string;
|
|
45
|
+
tags: string[];
|
|
46
|
+
draft: boolean;
|
|
47
|
+
featured: boolean;
|
|
48
|
+
seoTitle?: string | undefined;
|
|
49
|
+
updated?: string | undefined;
|
|
50
|
+
author?: string | string[] | undefined;
|
|
51
|
+
authors?: string[] | undefined;
|
|
52
|
+
category?: string | undefined;
|
|
53
|
+
image?: string | undefined;
|
|
54
|
+
coverImage?: string | undefined;
|
|
55
|
+
slug?: string | undefined;
|
|
56
|
+
lang?: string | undefined;
|
|
57
|
+
canonical?: string | undefined;
|
|
58
|
+
}>>;
|
|
59
|
+
export type BlogFrontmatter = z.infer<typeof blogFrontmatterSchema>;
|
|
60
|
+
export type BlogFrontmatterInput = z.input<typeof blogFrontmatterSchema>;
|
|
61
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAkBxB;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwB9B,CAAC;AAEL,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACpE,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// YAML (via gray-matter) parses bare dates like `2026-04-30` into Date objects
|
|
3
|
+
// and quoted ones into strings — accept both, emit ISO 8601.
|
|
4
|
+
const isoDate = z.union([z.string(), z.date()]).transform((value, ctx) => {
|
|
5
|
+
const parsed = value instanceof Date ? value : new Date(value);
|
|
6
|
+
if (Number.isNaN(parsed.getTime())) {
|
|
7
|
+
ctx.addIssue({
|
|
8
|
+
code: "custom",
|
|
9
|
+
message: `Invalid date "${String(value)}". Use ISO format (YYYY-MM-DD).`,
|
|
10
|
+
});
|
|
11
|
+
return z.NEVER;
|
|
12
|
+
}
|
|
13
|
+
return parsed.toISOString();
|
|
14
|
+
});
|
|
15
|
+
const nonEmpty = z.string().trim().min(1);
|
|
16
|
+
/**
|
|
17
|
+
* The shared frontmatter contract. Every post on every site validates against
|
|
18
|
+
* this; unknown keys are preserved-ignored (sites may carry extras locally).
|
|
19
|
+
*
|
|
20
|
+
* Normalizations: `coverImage` is accepted as an alias of `image`; `author`
|
|
21
|
+
* (string or list) and `authors` collapse into a single `authors: string[]`.
|
|
22
|
+
*/
|
|
23
|
+
export const blogFrontmatterSchema = z
|
|
24
|
+
.object({
|
|
25
|
+
title: nonEmpty,
|
|
26
|
+
seoTitle: nonEmpty.optional(),
|
|
27
|
+
description: nonEmpty,
|
|
28
|
+
date: isoDate,
|
|
29
|
+
updated: isoDate.optional(),
|
|
30
|
+
author: z.union([nonEmpty, z.array(nonEmpty)]).optional(),
|
|
31
|
+
authors: z.array(nonEmpty).optional(),
|
|
32
|
+
category: nonEmpty.optional(),
|
|
33
|
+
tags: z.array(nonEmpty).default([]),
|
|
34
|
+
image: nonEmpty.optional(),
|
|
35
|
+
coverImage: nonEmpty.optional(),
|
|
36
|
+
slug: nonEmpty.optional(),
|
|
37
|
+
draft: z.boolean().default(false),
|
|
38
|
+
featured: z.boolean().default(false),
|
|
39
|
+
// Reserved fields — validated now so adding behavior later isn't breaking.
|
|
40
|
+
lang: nonEmpty.optional(),
|
|
41
|
+
canonical: nonEmpty.optional(),
|
|
42
|
+
})
|
|
43
|
+
.transform(({ author, authors, coverImage, image, ...rest }) => ({
|
|
44
|
+
...rest,
|
|
45
|
+
image: image ?? coverImage,
|
|
46
|
+
authors: authors ?? (Array.isArray(author) ? author : author ? [author] : []),
|
|
47
|
+
}));
|
|
48
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+EAA+E;AAC/E,6DAA6D;AAC7D,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;IACxE,MAAM,MAAM,GAAG,KAAK,YAAY,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/D,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QACpC,GAAG,CAAC,QAAQ,CAAC;YACZ,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,iBAAiB,MAAM,CAAC,KAAK,CAAC,iCAAiC;SACxE,CAAC,CAAC;QACH,OAAO,CAAC,CAAC,KAAK,CAAC;IAChB,CAAC;IACD,OAAO,MAAM,CAAC,WAAW,EAAE,CAAC;AAC7B,CAAC,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AAE1C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC;KACpC,MAAM,CAAC;IACP,KAAK,EAAE,QAAQ;IACf,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE;IAC7B,WAAW,EAAE,QAAQ;IACrB,IAAI,EAAE,OAAO;IACb,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE;IAC3B,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACzD,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,QAAQ,EAAE;IACrC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE;IAC7B,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IACnC,KAAK,EAAE,QAAQ,CAAC,QAAQ,EAAE;IAC1B,UAAU,EAAE,QAAQ,CAAC,QAAQ,EAAE;IAC/B,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE;IACzB,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACjC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACpC,2EAA2E;IAC3E,IAAI,EAAE,QAAQ,CAAC,QAAQ,EAAE;IACzB,SAAS,EAAE,QAAQ,CAAC,QAAQ,EAAE;CAC9B,CAAC;KACD,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;IAChE,GAAG,IAAI;IACP,KAAK,EAAE,KAAK,IAAI,UAAU;IAC1B,OAAO,EAAE,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;CAC7E,CAAC,CAAC,CAAC"}
|
package/dist/seo.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type ArticleInput, type ArticleNode, type BreadcrumbListNode, type OrganizationInput, type WithContext } from "@ingram-tech/nk-seo";
|
|
2
|
+
import type { BlogPostPreview } from "./types.js";
|
|
3
|
+
export interface BlogSeoConfig {
|
|
4
|
+
/** Absolute site origin, e.g. "https://example.com". */
|
|
5
|
+
baseUrl: string;
|
|
6
|
+
/** Path prefix of the blog, e.g. "/posts" or "/blog". */
|
|
7
|
+
basePath: string;
|
|
8
|
+
/** Injected as the article publisher when provided. */
|
|
9
|
+
publisher?: OrganizationInput;
|
|
10
|
+
/** Crumb label for the blog index; defaults to "Blog". */
|
|
11
|
+
blogName?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function postUrl(post: BlogPostPreview, config: BlogSeoConfig): string;
|
|
14
|
+
/** BlogPosting JSON-LD for a post — the nk-seo bridge. */
|
|
15
|
+
export declare function blogPostArticle(post: BlogPostPreview, config: BlogSeoConfig, overrides?: Partial<ArticleInput>): WithContext<ArticleNode>;
|
|
16
|
+
/** Home → Blog → Post breadcrumb JSON-LD. */
|
|
17
|
+
export declare function blogPostBreadcrumbs(post: BlogPostPreview, config: BlogSeoConfig): WithContext<BreadcrumbListNode>;
|
|
18
|
+
//# sourceMappingURL=seo.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo.d.ts","sourceRoot":"","sources":["../src/seo.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,YAAY,EACjB,KAAK,WAAW,EAEhB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,WAAW,EAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,aAAa;IAC7B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,aAAa,GAAG,MAAM,CAE5E;AAKD,0DAA0D;AAC1D,wBAAgB,eAAe,CAC9B,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,aAAa,EACrB,SAAS,GAAE,OAAO,CAAC,YAAY,CAAM,GACnC,WAAW,CAAC,WAAW,CAAC,CAc1B;AAED,6CAA6C;AAC7C,wBAAgB,mBAAmB,CAClC,IAAI,EAAE,eAAe,EACrB,MAAM,EAAE,aAAa,GACnB,WAAW,CAAC,kBAAkB,CAAC,CAMjC"}
|
package/dist/seo.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { article, breadcrumbList, } from "@ingram-tech/nk-seo";
|
|
2
|
+
export function postUrl(post, config) {
|
|
3
|
+
return `${config.baseUrl}${config.basePath}/${post.slug}`;
|
|
4
|
+
}
|
|
5
|
+
const absoluteImage = (image, baseUrl) => image.startsWith("/") ? `${baseUrl}${image}` : image;
|
|
6
|
+
/** BlogPosting JSON-LD for a post — the nk-seo bridge. */
|
|
7
|
+
export function blogPostArticle(post, config, overrides = {}) {
|
|
8
|
+
return article({
|
|
9
|
+
type: "BlogPosting",
|
|
10
|
+
headline: post.title,
|
|
11
|
+
description: post.description,
|
|
12
|
+
url: post.canonical ?? postUrl(post, config),
|
|
13
|
+
datePublished: post.date,
|
|
14
|
+
dateModified: post.updated,
|
|
15
|
+
authors: post.authors.map((name) => ({ name })),
|
|
16
|
+
image: post.image ? absoluteImage(post.image, config.baseUrl) : undefined,
|
|
17
|
+
keywords: post.tags.length ? post.tags : undefined,
|
|
18
|
+
publisher: config.publisher,
|
|
19
|
+
...overrides,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/** Home → Blog → Post breadcrumb JSON-LD. */
|
|
23
|
+
export function blogPostBreadcrumbs(post, config) {
|
|
24
|
+
return breadcrumbList([
|
|
25
|
+
{ name: "Home", url: config.baseUrl },
|
|
26
|
+
{ name: config.blogName ?? "Blog", url: `${config.baseUrl}${config.basePath}` },
|
|
27
|
+
{ name: post.title, url: postUrl(post, config) },
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=seo.js.map
|
package/dist/seo.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"seo.js","sourceRoot":"","sources":["../src/seo.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,OAAO,EAGP,cAAc,GAId,MAAM,qBAAqB,CAAC;AAc7B,MAAM,UAAU,OAAO,CAAC,IAAqB,EAAE,MAAqB;IACnE,OAAO,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;AAC3D,CAAC;AAED,MAAM,aAAa,GAAG,CAAC,KAAa,EAAE,OAAe,EAAU,EAAE,CAChE,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;AAEtD,0DAA0D;AAC1D,MAAM,UAAU,eAAe,CAC9B,IAAqB,EACrB,MAAqB,EACrB,YAAmC,EAAE;IAErC,OAAO,OAAO,CAAC;QACd,IAAI,EAAE,aAAa;QACnB,QAAQ,EAAE,IAAI,CAAC,KAAK;QACpB,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,GAAG,EAAE,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;QAC5C,aAAa,EAAE,IAAI,CAAC,IAAI;QACxB,YAAY,EAAE,IAAI,CAAC,OAAO;QAC1B,OAAO,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;QAC/C,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS;QACzE,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;QAClD,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,GAAG,SAAS;KACZ,CAAC,CAAC;AACJ,CAAC;AAED,6CAA6C;AAC7C,MAAM,UAAU,mBAAmB,CAClC,IAAqB,EACrB,MAAqB;IAErB,OAAO,cAAc,CAAC;QACrB,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,CAAC,OAAO,EAAE;QACrC,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,IAAI,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC,OAAO,GAAG,MAAM,CAAC,QAAQ,EAAE,EAAE;QAC/E,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;KAChD,CAAC,CAAC;AACJ,CAAC"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createBlog, parsePost, parsePostFileName, type Blog, type BlogConfig, type BlogSource, type RawPostFile, } from "./blog.js";
|
|
2
|
+
export { fsSource } from "./sources/fs.js";
|
|
3
|
+
export { githubSource, type GitHubRepoConfig } from "./sources/github.js";
|
|
4
|
+
export { publishPost, serializePost, type PublishedPost, type PublishPostInput, } from "./sources/publish.js";
|
|
5
|
+
export { generateRss, type RssConfig } from "./rss.js";
|
|
6
|
+
export { keys } from "./keys.js";
|
|
7
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EACN,UAAU,EACV,SAAS,EACT,iBAAiB,EACjB,KAAK,IAAI,EACT,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,WAAW,GAChB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAE,KAAK,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EACN,WAAW,EACX,aAAa,EACb,KAAK,aAAa,EAClB,KAAK,gBAAgB,GACrB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Server entry: everything that touches the filesystem, the GitHub API, or
|
|
2
|
+
// env. Import from pages/route handlers/scripts only — never from client
|
|
3
|
+
// components (the root "." export carries the types you need there).
|
|
4
|
+
export { createBlog, parsePost, parsePostFileName, } from "./blog.js";
|
|
5
|
+
export { fsSource } from "./sources/fs.js";
|
|
6
|
+
export { githubSource } from "./sources/github.js";
|
|
7
|
+
export { publishPost, serializePost, } from "./sources/publish.js";
|
|
8
|
+
export { generateRss } from "./rss.js";
|
|
9
|
+
export { keys } from "./keys.js";
|
|
10
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,yEAAyE;AACzE,qEAAqE;AACrE,OAAO,EACN,UAAU,EACV,SAAS,EACT,iBAAiB,GAKjB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,YAAY,EAAyB,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EACN,WAAW,EACX,aAAa,GAGb,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,WAAW,EAAkB,MAAM,UAAU,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { BlogSource } from "../blog.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build-time filesystem source. The blog contract is full SSG: call this from
|
|
4
|
+
* `generateStaticParams`-driven pages (with `dynamicParams = false`), sitemap
|
|
5
|
+
* and RSS route handlers — all of which run at build. It must NOT be relied on
|
|
6
|
+
* at request time on serverless hosts: output file tracing does not follow
|
|
7
|
+
* `fs` reads made inside a published package, so the content directory may be
|
|
8
|
+
* absent from the deployed function (`outputFileTracingIncludes` is the escape
|
|
9
|
+
* hatch if you truly need runtime reads).
|
|
10
|
+
*/
|
|
11
|
+
export declare function fsSource(dir: string): BlogSource;
|
|
12
|
+
//# sourceMappingURL=fs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs.d.ts","sourceRoot":"","sources":["../../src/sources/fs.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAe,MAAM,YAAY,CAAC;AAE1D;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CA6ChD"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Build-time filesystem source. The blog contract is full SSG: call this from
|
|
5
|
+
* `generateStaticParams`-driven pages (with `dynamicParams = false`), sitemap
|
|
6
|
+
* and RSS route handlers — all of which run at build. It must NOT be relied on
|
|
7
|
+
* at request time on serverless hosts: output file tracing does not follow
|
|
8
|
+
* `fs` reads made inside a published package, so the content directory may be
|
|
9
|
+
* absent from the deployed function (`outputFileTracingIncludes` is the escape
|
|
10
|
+
* hatch if you truly need runtime reads).
|
|
11
|
+
*/
|
|
12
|
+
export function fsSource(dir) {
|
|
13
|
+
const root = path.isAbsolute(dir) ? dir : path.join(process.cwd(), dir);
|
|
14
|
+
return {
|
|
15
|
+
async load() {
|
|
16
|
+
let entries;
|
|
17
|
+
try {
|
|
18
|
+
entries = await fs.readdir(root, { withFileTypes: true });
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error instanceof Error &&
|
|
22
|
+
"code" in error &&
|
|
23
|
+
error.code === "ENOENT") {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
const names = [];
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
|
|
31
|
+
names.push(entry.name);
|
|
32
|
+
}
|
|
33
|
+
else if (entry.isDirectory()) {
|
|
34
|
+
// Folder post: <slug>/index.md(x) with colocated assets/code.
|
|
35
|
+
for (const index of ["index.mdx", "index.md"]) {
|
|
36
|
+
try {
|
|
37
|
+
await fs.access(path.join(root, entry.name, index));
|
|
38
|
+
names.push(`${entry.name}/${index}`);
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// keep looking
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return Promise.all(names.map(async (name) => ({
|
|
48
|
+
name,
|
|
49
|
+
content: await fs.readFile(path.join(root, name), "utf8"),
|
|
50
|
+
})));
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=fs.js.map
|