@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.
Files changed (94) hide show
  1. package/README.md +78 -0
  2. package/dist/blog.d.ts +54 -0
  3. package/dist/blog.d.ts.map +1 -0
  4. package/dist/blog.js +99 -0
  5. package/dist/blog.js.map +1 -0
  6. package/dist/contract.d.ts +102 -0
  7. package/dist/contract.d.ts.map +1 -0
  8. package/dist/contract.js +57 -0
  9. package/dist/contract.js.map +1 -0
  10. package/dist/date.d.ts +6 -0
  11. package/dist/date.d.ts.map +1 -0
  12. package/dist/date.js +12 -0
  13. package/dist/date.js.map +1 -0
  14. package/dist/http.d.ts +3 -0
  15. package/dist/http.d.ts.map +1 -0
  16. package/dist/http.js +10 -0
  17. package/dist/http.js.map +1 -0
  18. package/dist/index.d.ts +7 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +10 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/keys.d.ts +8 -0
  23. package/dist/keys.d.ts.map +1 -0
  24. package/dist/keys.js +11 -0
  25. package/dist/keys.js.map +1 -0
  26. package/dist/limited-mdx.d.ts +33 -0
  27. package/dist/limited-mdx.d.ts.map +1 -0
  28. package/dist/limited-mdx.js +151 -0
  29. package/dist/limited-mdx.js.map +1 -0
  30. package/dist/reading-time.d.ts +10 -0
  31. package/dist/reading-time.d.ts.map +1 -0
  32. package/dist/reading-time.js +11 -0
  33. package/dist/reading-time.js.map +1 -0
  34. package/dist/render.d.ts +38 -0
  35. package/dist/render.d.ts.map +1 -0
  36. package/dist/render.js +39 -0
  37. package/dist/render.js.map +1 -0
  38. package/dist/rss.d.ts +18 -0
  39. package/dist/rss.d.ts.map +1 -0
  40. package/dist/rss.js +49 -0
  41. package/dist/rss.js.map +1 -0
  42. package/dist/schema.d.ts +61 -0
  43. package/dist/schema.d.ts.map +1 -0
  44. package/dist/schema.js +48 -0
  45. package/dist/schema.js.map +1 -0
  46. package/dist/seo.d.ts +18 -0
  47. package/dist/seo.d.ts.map +1 -0
  48. package/dist/seo.js +30 -0
  49. package/dist/seo.js.map +1 -0
  50. package/dist/server.d.ts +7 -0
  51. package/dist/server.d.ts.map +1 -0
  52. package/dist/server.js +10 -0
  53. package/dist/server.js.map +1 -0
  54. package/dist/sources/fs.d.ts +12 -0
  55. package/dist/sources/fs.d.ts.map +1 -0
  56. package/dist/sources/fs.js +54 -0
  57. package/dist/sources/fs.js.map +1 -0
  58. package/dist/sources/github.d.ts +20 -0
  59. package/dist/sources/github.d.ts.map +1 -0
  60. package/dist/sources/github.js +62 -0
  61. package/dist/sources/github.js.map +1 -0
  62. package/dist/sources/publish.d.ts +36 -0
  63. package/dist/sources/publish.d.ts.map +1 -0
  64. package/dist/sources/publish.js +55 -0
  65. package/dist/sources/publish.js.map +1 -0
  66. package/dist/types.d.ts +33 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/types.js +2 -0
  69. package/dist/types.js.map +1 -0
  70. package/dist/unstyled/callout.d.ts +8 -0
  71. package/dist/unstyled/callout.d.ts.map +1 -0
  72. package/dist/unstyled/callout.js +8 -0
  73. package/dist/unstyled/callout.js.map +1 -0
  74. package/dist/unstyled/figure.d.ts +8 -0
  75. package/dist/unstyled/figure.d.ts.map +1 -0
  76. package/dist/unstyled/figure.js +9 -0
  77. package/dist/unstyled/figure.js.map +1 -0
  78. package/dist/unstyled/index.d.ts +16 -0
  79. package/dist/unstyled/index.d.ts.map +1 -0
  80. package/dist/unstyled/index.js +23 -0
  81. package/dist/unstyled/index.js.map +1 -0
  82. package/dist/unstyled/newsletter-subscribe.d.ts +7 -0
  83. package/dist/unstyled/newsletter-subscribe.d.ts.map +1 -0
  84. package/dist/unstyled/newsletter-subscribe.js +7 -0
  85. package/dist/unstyled/newsletter-subscribe.js.map +1 -0
  86. package/dist/unstyled/tweet.d.ts +8 -0
  87. package/dist/unstyled/tweet.d.ts.map +1 -0
  88. package/dist/unstyled/tweet.js +8 -0
  89. package/dist/unstyled/tweet.js.map +1 -0
  90. package/dist/unstyled/youtube.d.ts +7 -0
  91. package/dist/unstyled/youtube.d.ts.map +1 -0
  92. package/dist/unstyled/youtube.js +11 -0
  93. package/dist/unstyled/youtube.js.map +1 -0
  94. package/package.json +78 -0
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # @ingram-tech/nk-blog
2
+
3
+ File-based blogs for Next.js sites: one frontmatter contract, a build-time
4
+ reader, `.md`/limited-`.mdx` rendering, a typed component vocabulary with
5
+ unstyled defaults, GitHub read/publish for automated publishers, and RSS.
6
+
7
+ **The files are the index.** Posts are `content/blog/<slug>.md` (or `.mdx`,
8
+ or `<slug>/index.mdx` folders) with YAML frontmatter. There is no generated
9
+ `posts.json`, no metadata-extraction script, and nothing to drift.
10
+
11
+ ## Entry points
12
+
13
+ | Import | Contents | Where |
14
+ |---|---|---|
15
+ | `@ingram-tech/nk-blog` | frontmatter schema, types, vocabulary manifest, reading time, dates, JSON-LD bridge | anywhere |
16
+ | `@ingram-tech/nk-blog/server` | `createBlog`, `fsSource`, `githubSource`, `publishPost`, `serializePost`, `generateRss`, `keys` | server only |
17
+ | `@ingram-tech/nk-blog/render` | `PostBody`, `MarkdownBody`, `MdxBody`, `remarkLimitedMdx`, `validateLimitedMdx` | server components |
18
+ | `@ingram-tech/nk-blog/unstyled` | behavior-correct, zero-styling vocabulary defaults | server components |
19
+
20
+ ## Site setup
21
+
22
+ ```ts
23
+ // src/lib/blog.ts
24
+ import { createBlog, fsSource } from "@ingram-tech/nk-blog/server";
25
+
26
+ export const blog = createBlog({
27
+ source: fsSource("content/blog"),
28
+ defaultAuthor: "Example Team",
29
+ drafts: process.env.NODE_ENV !== "production",
30
+ });
31
+ ```
32
+
33
+ ```tsx
34
+ // src/lib/blog-components.tsx — the site's entire vocabulary obligation
35
+ import { defineBlogComponents } from "@ingram-tech/nk-blog";
36
+ import { unstyled } from "@ingram-tech/nk-blog/unstyled";
37
+
38
+ export const blogComponents = defineBlogComponents({
39
+ ...unstyled,
40
+ // Callout: BrandCallout, — replace wholesale where the brand cares
41
+ });
42
+ ```
43
+
44
+ ```tsx
45
+ // src/app/blog/[slug]/page.tsx — full SSG, no runtime fs
46
+ import { PostBody } from "@ingram-tech/nk-blog/render";
47
+ import { blog } from "@/lib/blog";
48
+ import { blogComponents } from "@/lib/blog-components";
49
+ import { mdxElements } from "@/mdx-components"; // the site's own element map
50
+
51
+ export const dynamicParams = false;
52
+ export async function generateStaticParams() {
53
+ return (await blog.slugs()).map((slug) => ({ slug }));
54
+ }
55
+
56
+ export default async function Post({ params }: PageProps<"/blog/[slug]">) {
57
+ const { slug } = await params;
58
+ const post = await blog.post(slug);
59
+ if (!post) notFound();
60
+ return <PostBody post={post} components={blogComponents} elements={mdxElements} />;
61
+ }
62
+ ```
63
+
64
+ ## The rules that keep this portable
65
+
66
+ - **Vocabulary, not imports.** A Tier-1 post references `<Callout>`, `<Figure>`,
67
+ `<YouTube>`, `<Tweet>`, `<NewsletterSubscribe>` by bare name and nothing
68
+ else. `render` enforces this in the AST (`remarkLimitedMdx`): no ESM, no
69
+ `{…}` expressions, literal props only. Automated publishers emit `.md` (pure
70
+ data) and validate `.mdx` with `validateLimitedMdx` before committing.
71
+ - **Tier-2 (bespoke) posts** are folders with human-reviewed `components.tsx`,
72
+ passed via `bespoke` — which relaxes enforcement, because a human merged it.
73
+ - **No pixels in this package.** The unstyled defaults carry semantics and
74
+ behavior only and accept no visual props beyond `className`. Want variation?
75
+ Replace the component in your registry — never add props here.
76
+ - **Build-time only.** `fsSource` runs under `generateStaticParams` /
77
+ `dynamicParams = false`; serverless functions must not read post files at
78
+ request time (output tracing won't include them).
package/dist/blog.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import type { BlogPost, BlogPostPreview, PostFormat } from "./types.js";
2
+ /** A content file, named relative to the content dir ("slug.md", "slug/index.mdx"). */
3
+ export interface RawPostFile {
4
+ name: string;
5
+ content: string;
6
+ }
7
+ export interface BlogSource {
8
+ load(): Promise<RawPostFile[]>;
9
+ }
10
+ export interface BlogConfig {
11
+ source: BlogSource;
12
+ /** Byline when a post declares none (each site sets its own). */
13
+ defaultAuthor?: string;
14
+ defaultCategory?: string;
15
+ wordsPerMinute?: number;
16
+ /** Include drafts (listing pages typically pass `NODE_ENV !== "production"`). */
17
+ drafts?: boolean;
18
+ /**
19
+ * Site-specific image fallback (e.g. "look in /public/images/posts/<slug>").
20
+ * This is config precisely so per-site divergence never forks the reader.
21
+ */
22
+ resolveImage?: (post: {
23
+ slug: string;
24
+ image?: string;
25
+ }) => string | undefined;
26
+ /**
27
+ * "throw" (default) fails the build on malformed frontmatter — a real post
28
+ * silently missing from a site is worse than a red build. Remote sources
29
+ * (admin listing over GitHub) prefer "skip" to keep one bad file from
30
+ * hiding a whole target.
31
+ */
32
+ onInvalid?: "throw" | "skip";
33
+ }
34
+ export interface Blog {
35
+ /** All non-draft posts, newest first, with bodies. */
36
+ posts(): Promise<BlogPost[]>;
37
+ /** All non-draft posts, newest first, without bodies. */
38
+ previews(): Promise<BlogPostPreview[]>;
39
+ post(slug: string): Promise<BlogPost | null>;
40
+ slugs(): Promise<string[]>;
41
+ /** The pinned (`featured: true`) post, else the newest. */
42
+ featured(): Promise<BlogPost | null>;
43
+ }
44
+ interface ParsedName {
45
+ slug: string;
46
+ format: PostFormat;
47
+ draftByName: boolean;
48
+ }
49
+ /** "slug.md", "slug.mdx", "slug/index.md(x)" → slug + format; else null. */
50
+ export declare function parsePostFileName(name: string): ParsedName | null;
51
+ export declare function parsePost(file: RawPostFile, config: BlogConfig): BlogPost | null;
52
+ export declare function createBlog(config: BlogConfig): Blog;
53
+ export {};
54
+ //# sourceMappingURL=blog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog.d.ts","sourceRoot":"","sources":["../src/blog.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExE,uFAAuF;AACvF,MAAM,WAAW,WAAW;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IAC1B,IAAI,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,iEAAiE;IACjE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iFAAiF;IACjF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,MAAM,GAAG,SAAS,CAAC;IAC9E;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,IAAI;IACpB,sDAAsD;IACtD,KAAK,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAC;IAC7B,yDAAyD;IACzD,QAAQ,IAAI,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;IAC7C,KAAK,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC3B,2DAA2D;IAC3D,QAAQ,IAAI,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAAC;CACrC;AAID,UAAU,UAAU;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,OAAO,CAAC;CACrB;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAYjE;AAED,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,UAAU,GAAG,QAAQ,GAAG,IAAI,CAuDhF;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAmCnD"}
package/dist/blog.js ADDED
@@ -0,0 +1,99 @@
1
+ import matter from "gray-matter";
2
+ import { blogFrontmatterSchema } from "./schema.js";
3
+ import { DEFAULT_WORDS_PER_MINUTE, readingTime } from "./reading-time.js";
4
+ const POST_FILE = /^(?:(?<flat>[^/]+)|(?<dir>[^/]+)\/index)\.(?<ext>mdx?)$/;
5
+ /** "slug.md", "slug.mdx", "slug/index.md(x)" → slug + format; else null. */
6
+ export function parsePostFileName(name) {
7
+ const match = POST_FILE.exec(name);
8
+ const groups = match?.groups;
9
+ if (!groups)
10
+ return null;
11
+ const base = groups.flat ?? groups.dir;
12
+ if (!base)
13
+ return null;
14
+ return {
15
+ slug: base.replace(/^_/, ""),
16
+ format: groups.ext === "mdx" ? "mdx" : "md",
17
+ // Legacy `_draft.md` convention, honored as an alias of `draft: true`.
18
+ draftByName: base.startsWith("_"),
19
+ };
20
+ }
21
+ export function parsePost(file, config) {
22
+ const named = parsePostFileName(file.name);
23
+ if (!named)
24
+ return null;
25
+ const { data, content } = matter(file.content);
26
+ const result = blogFrontmatterSchema.safeParse(data);
27
+ if (!result.success) {
28
+ if ((config.onInvalid ?? "throw") === "skip") {
29
+ console.warn(`nk-blog: skipping ${file.name}: ${result.error.issues
30
+ .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
31
+ .join("; ")}`);
32
+ return null;
33
+ }
34
+ throw new Error(`nk-blog: invalid frontmatter in ${file.name}: ${result.error.issues
35
+ .map((issue) => `${issue.path.join(".")}: ${issue.message}`)
36
+ .join("; ")}`);
37
+ }
38
+ const frontmatter = result.data;
39
+ const slug = frontmatter.slug ?? named.slug;
40
+ const body = content.trim();
41
+ const authors = frontmatter.authors.length
42
+ ? frontmatter.authors
43
+ : config.defaultAuthor
44
+ ? [config.defaultAuthor]
45
+ : [];
46
+ const time = readingTime(body, config.wordsPerMinute ?? DEFAULT_WORDS_PER_MINUTE);
47
+ const image = config.resolveImage?.({ slug, image: frontmatter.image }) ?? frontmatter.image;
48
+ return {
49
+ slug,
50
+ title: frontmatter.title,
51
+ seoTitle: frontmatter.seoTitle,
52
+ description: frontmatter.description,
53
+ date: frontmatter.date,
54
+ updated: frontmatter.updated,
55
+ authors,
56
+ author: authors[0] ?? "",
57
+ category: frontmatter.category ?? config.defaultCategory,
58
+ tags: frontmatter.tags,
59
+ image,
60
+ draft: frontmatter.draft || named.draftByName,
61
+ featured: frontmatter.featured,
62
+ lang: frontmatter.lang,
63
+ canonical: frontmatter.canonical,
64
+ format: named.format,
65
+ readingTimeMinutes: time.minutes,
66
+ readTime: time.text,
67
+ content: body,
68
+ };
69
+ }
70
+ export function createBlog(config) {
71
+ const posts = async () => {
72
+ const files = await config.source.load();
73
+ const parsed = files
74
+ .map((file) => parsePost(file, config))
75
+ .filter((post) => post !== null)
76
+ .filter((post) => config.drafts === true || !post.draft);
77
+ const seen = new Map();
78
+ for (const post of parsed) {
79
+ if (seen.has(post.slug)) {
80
+ // Two files resolving to one slug is a routing conflict — always
81
+ // a loud build failure, never a quiet last-one-wins.
82
+ throw new Error(`nk-blog: duplicate slug "${post.slug}"`);
83
+ }
84
+ seen.set(post.slug, post);
85
+ }
86
+ return [...seen.values()].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
87
+ };
88
+ return {
89
+ posts,
90
+ previews: async () => (await posts()).map(({ content: _content, ...preview }) => preview),
91
+ post: async (slug) => (await posts()).find((candidate) => candidate.slug === slug) ?? null,
92
+ slugs: async () => (await posts()).map((post) => post.slug),
93
+ featured: async () => {
94
+ const all = await posts();
95
+ return all.find((post) => post.featured) ?? all[0] ?? null;
96
+ },
97
+ };
98
+ }
99
+ //# sourceMappingURL=blog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"blog.js","sourceRoot":"","sources":["../src/blog.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,EAAE,wBAAwB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AA8C1E,MAAM,SAAS,GAAG,yDAAyD,CAAC;AAQ5E,4EAA4E;AAC5E,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC7C,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,KAAK,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC;IACvC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,OAAO;QACN,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5B,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI;QAC3C,uEAAuE;QACvE,WAAW,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;KACjC,CAAC;AACH,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,IAAiB,EAAE,MAAkB;IAC9D,MAAM,KAAK,GAAG,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC3C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IAExB,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,KAAK,MAAM,EAAE,CAAC;YAC9C,OAAO,CAAC,IAAI,CACX,qBAAqB,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,KAAK,CAAC,MAAM;iBACpD,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;iBAC3D,IAAI,CAAC,IAAI,CAAC,EAAE,CACd,CAAC;YACF,OAAO,IAAI,CAAC;QACb,CAAC;QACD,MAAM,IAAI,KAAK,CACd,mCAAmC,IAAI,CAAC,IAAI,KAAK,MAAM,CAAC,KAAK,CAAC,MAAM;aAClE,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;aAC3D,IAAI,CAAC,IAAI,CAAC,EAAE,CACd,CAAC;IACH,CAAC;IAED,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;IAChC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;IAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC,MAAM;QACzC,CAAC,CAAC,WAAW,CAAC,OAAO;QACrB,CAAC,CAAC,MAAM,CAAC,aAAa;YACrB,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,CAAC;YACxB,CAAC,CAAC,EAAE,CAAC;IACP,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,EAAE,MAAM,CAAC,cAAc,IAAI,wBAAwB,CAAC,CAAC;IAClF,MAAM,KAAK,GACV,MAAM,CAAC,YAAY,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,CAAC,KAAK,EAAE,CAAC,IAAI,WAAW,CAAC,KAAK,CAAC;IAEhF,OAAO;QACN,IAAI;QACJ,KAAK,EAAE,WAAW,CAAC,KAAK;QACxB,QAAQ,EAAE,WAAW,CAAC,QAAQ;QAC9B,WAAW,EAAE,WAAW,CAAC,WAAW;QACpC,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,OAAO,EAAE,WAAW,CAAC,OAAO;QAC5B,OAAO;QACP,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE;QACxB,QAAQ,EAAE,WAAW,CAAC,QAAQ,IAAI,MAAM,CAAC,eAAe;QACxD,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,KAAK;QACL,KAAK,EAAE,WAAW,CAAC,KAAK,IAAI,KAAK,CAAC,WAAW;QAC7C,QAAQ,EAAE,WAAW,CAAC,QAAQ;QAC9B,IAAI,EAAE,WAAW,CAAC,IAAI;QACtB,SAAS,EAAE,WAAW,CAAC,SAAS;QAChC,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,kBAAkB,EAAE,IAAI,CAAC,OAAO;QAChC,QAAQ,EAAE,IAAI,CAAC,IAAI;QACnB,OAAO,EAAE,IAAI;KACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,MAAkB;IAC5C,MAAM,KAAK,GAAG,KAAK,IAAyB,EAAE;QAC7C,MAAM,KAAK,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACzC,MAAM,MAAM,GAAG,KAAK;aAClB,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;aACtC,MAAM,CAAC,CAAC,IAAI,EAAoB,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;aACjD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAE1D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAoB,CAAC;QACzC,KAAK,MAAM,IAAI,IAAI,MAAM,EAAE,CAAC;YAC3B,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzB,iEAAiE;gBACjE,qDAAqD;gBACrD,MAAM,IAAI,KAAK,CAAC,4BAA4B,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;YAC3D,CAAC;YACD,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC3B,CAAC;QAED,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,IAAI,CAC7B,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CACjE,CAAC;IACH,CAAC,CAAC;IAEF,OAAO;QACN,KAAK;QACL,QAAQ,EAAE,KAAK,IAAI,EAAE,CACpB,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC;QACpE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,CACpB,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,IAAI;QACrE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3D,QAAQ,EAAE,KAAK,IAAI,EAAE;YACpB,MAAM,GAAG,GAAG,MAAM,KAAK,EAAE,CAAC;YAC1B,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;QAC5D,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,102 @@
1
+ import type { ComponentType, ReactNode } from "react";
2
+ import { z } from "zod";
3
+ /**
4
+ * The vocabulary manifest — the cross-site component contract.
5
+ *
6
+ * A Tier-1 post may reference exactly these components by bare name. The
7
+ * package owns the names and prop schemas; each site owns the pixels (see
8
+ * `@ingram-tech/nk-blog/unstyled` for the behavior-correct defaults). The
9
+ * admin publisher validates a post against this manifest (at the version the
10
+ * target site pins) before committing it.
11
+ */
12
+ export declare const VOCABULARY: readonly ["Callout", "Figure", "YouTube", "Tweet", "NewsletterSubscribe"];
13
+ export type VocabularyName = (typeof VOCABULARY)[number];
14
+ export declare const calloutProps: z.ZodObject<{
15
+ variant: z.ZodDefault<z.ZodEnum<{
16
+ note: "note";
17
+ tip: "tip";
18
+ warning: "warning";
19
+ important: "important";
20
+ }>>;
21
+ title: z.ZodOptional<z.ZodString>;
22
+ }, z.core.$strip>;
23
+ export declare const figureProps: z.ZodObject<{
24
+ src: z.ZodString;
25
+ alt: z.ZodString;
26
+ caption: z.ZodOptional<z.ZodString>;
27
+ width: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
28
+ height: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
29
+ }, z.core.$strip>;
30
+ export declare const youTubeProps: z.ZodObject<{
31
+ id: z.ZodString;
32
+ title: z.ZodOptional<z.ZodString>;
33
+ start: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
34
+ }, z.core.$strip>;
35
+ export declare const tweetProps: z.ZodObject<{
36
+ id: z.ZodString;
37
+ }, z.core.$strip>;
38
+ export declare const newsletterSubscribeProps: z.ZodObject<{
39
+ action: z.ZodString;
40
+ placeholder: z.ZodOptional<z.ZodString>;
41
+ buttonLabel: z.ZodOptional<z.ZodString>;
42
+ }, z.core.$strip>;
43
+ /** Machine-checkable side of the contract, keyed by component name. */
44
+ export declare const vocabularyProps: {
45
+ Callout: z.ZodObject<{
46
+ variant: z.ZodDefault<z.ZodEnum<{
47
+ note: "note";
48
+ tip: "tip";
49
+ warning: "warning";
50
+ important: "important";
51
+ }>>;
52
+ title: z.ZodOptional<z.ZodString>;
53
+ }, z.core.$strip>;
54
+ Figure: z.ZodObject<{
55
+ src: z.ZodString;
56
+ alt: z.ZodString;
57
+ caption: z.ZodOptional<z.ZodString>;
58
+ width: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
59
+ height: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
60
+ }, z.core.$strip>;
61
+ YouTube: z.ZodObject<{
62
+ id: z.ZodString;
63
+ title: z.ZodOptional<z.ZodString>;
64
+ start: z.ZodOptional<z.ZodUnion<readonly [z.ZodNumber, z.ZodString]>>;
65
+ }, z.core.$strip>;
66
+ Tweet: z.ZodObject<{
67
+ id: z.ZodString;
68
+ }, z.core.$strip>;
69
+ NewsletterSubscribe: z.ZodObject<{
70
+ action: z.ZodString;
71
+ placeholder: z.ZodOptional<z.ZodString>;
72
+ buttonLabel: z.ZodOptional<z.ZodString>;
73
+ }, z.core.$strip>;
74
+ };
75
+ type WithReactExtras<T> = T & {
76
+ children?: ReactNode;
77
+ className?: string;
78
+ };
79
+ export type CalloutProps = WithReactExtras<z.input<typeof calloutProps>>;
80
+ export type FigureProps = WithReactExtras<z.input<typeof figureProps>>;
81
+ export type YouTubeProps = WithReactExtras<z.input<typeof youTubeProps>>;
82
+ export type TweetProps = WithReactExtras<z.input<typeof tweetProps>>;
83
+ export type NewsletterSubscribeProps = WithReactExtras<z.input<typeof newsletterSubscribeProps>>;
84
+ export interface VocabularyPropsMap {
85
+ Callout: CalloutProps;
86
+ Figure: FigureProps;
87
+ YouTube: YouTubeProps;
88
+ Tweet: TweetProps;
89
+ NewsletterSubscribe: NewsletterSubscribeProps;
90
+ }
91
+ /**
92
+ * The exhaustive registry a site must provide. Exhaustiveness is the point:
93
+ * a site missing a vocabulary component fails `tsc`, so an admin-published
94
+ * post can never reference something a site cannot render.
95
+ */
96
+ export type BlogComponents = {
97
+ [K in VocabularyName]: ComponentType<VocabularyPropsMap[K]>;
98
+ };
99
+ /** Identity helper — exists purely for inference and error locality. */
100
+ export declare function defineBlogComponents(components: BlogComponents): BlogComponents;
101
+ export {};
102
+ //# sourceMappingURL=contract.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;GAQG;AACH,eAAO,MAAM,UAAU,2EAMb,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAMzD,eAAO,MAAM,YAAY;;;;;;;;iBAGvB,CAAC;AAEH,eAAO,MAAM,WAAW;;;;;;iBAMtB,CAAC;AAEH,eAAO,MAAM,YAAY;;;;iBAIvB,CAAC;AAEH,eAAO,MAAM,UAAU;;iBAErB,CAAC;AAEH,eAAO,MAAM,wBAAwB;;;;iBAInC,CAAC;AAEH,uEAAuE;AACvE,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAMiB,CAAC;AAI9C,KAAK,eAAe,CAAC,CAAC,IAAI,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AACzE,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC;AACvE,MAAM,MAAM,YAAY,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC;AACzE,MAAM,MAAM,UAAU,GAAG,eAAe,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,UAAU,CAAC,CAAC,CAAC;AACrE,MAAM,MAAM,wBAAwB,GAAG,eAAe,CACrD,CAAC,CAAC,KAAK,CAAC,OAAO,wBAAwB,CAAC,CACxC,CAAC;AAEF,MAAM,WAAW,kBAAkB;IAClC,OAAO,EAAE,YAAY,CAAC;IACtB,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,UAAU,CAAC;IAClB,mBAAmB,EAAE,wBAAwB,CAAC;CAC9C;AAED;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG;KAC3B,CAAC,IAAI,cAAc,GAAG,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC;CAC3D,CAAC;AAEF,wEAAwE;AACxE,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,cAAc,GAAG,cAAc,CAE/E"}
@@ -0,0 +1,57 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * The vocabulary manifest — the cross-site component contract.
4
+ *
5
+ * A Tier-1 post may reference exactly these components by bare name. The
6
+ * package owns the names and prop schemas; each site owns the pixels (see
7
+ * `@ingram-tech/nk-blog/unstyled` for the behavior-correct defaults). The
8
+ * admin publisher validates a post against this manifest (at the version the
9
+ * target site pins) before committing it.
10
+ */
11
+ export const VOCABULARY = [
12
+ "Callout",
13
+ "Figure",
14
+ "YouTube",
15
+ "Tweet",
16
+ "NewsletterSubscribe",
17
+ ];
18
+ // MDX literal attributes arrive as strings (`width="1200"`), so dimension-ish
19
+ // props coerce rather than demand numbers.
20
+ const dimension = z.union([z.number(), z.string().regex(/^\d+$/)]);
21
+ export const calloutProps = z.object({
22
+ variant: z.enum(["note", "tip", "warning", "important"]).default("note"),
23
+ title: z.string().optional(),
24
+ });
25
+ export const figureProps = z.object({
26
+ src: z.string().min(1),
27
+ alt: z.string(),
28
+ caption: z.string().optional(),
29
+ width: dimension.optional(),
30
+ height: dimension.optional(),
31
+ });
32
+ export const youTubeProps = z.object({
33
+ id: z.string().min(1),
34
+ title: z.string().optional(),
35
+ start: dimension.optional(),
36
+ });
37
+ export const tweetProps = z.object({
38
+ id: z.string().min(1),
39
+ });
40
+ export const newsletterSubscribeProps = z.object({
41
+ action: z.string().min(1),
42
+ placeholder: z.string().optional(),
43
+ buttonLabel: z.string().optional(),
44
+ });
45
+ /** Machine-checkable side of the contract, keyed by component name. */
46
+ export const vocabularyProps = {
47
+ Callout: calloutProps,
48
+ Figure: figureProps,
49
+ YouTube: youTubeProps,
50
+ Tweet: tweetProps,
51
+ NewsletterSubscribe: newsletterSubscribeProps,
52
+ };
53
+ /** Identity helper — exists purely for inference and error locality. */
54
+ export function defineBlogComponents(components) {
55
+ return components;
56
+ }
57
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG;IACzB,SAAS;IACT,QAAQ;IACR,SAAS;IACT,OAAO;IACP,qBAAqB;CACZ,CAAC;AAIX,8EAA8E;AAC9E,2CAA2C;AAC3C,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;AAEnE,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IACxE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACtB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE;IAC3B,MAAM,EAAE,SAAS,CAAC,QAAQ,EAAE;CAC5B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACpC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACrB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,KAAK,EAAE,SAAS,CAAC,QAAQ,EAAE;CAC3B,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;CACrB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACzB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAClC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,eAAe,GAAG;IAC9B,OAAO,EAAE,YAAY;IACrB,MAAM,EAAE,WAAW;IACnB,OAAO,EAAE,YAAY;IACrB,KAAK,EAAE,UAAU;IACjB,mBAAmB,EAAE,wBAAwB;CACD,CAAC;AA+B9C,wEAAwE;AACxE,MAAM,UAAU,oBAAoB,CAAC,UAA0B;IAC9D,OAAO,UAAU,CAAC;AACnB,CAAC"}
package/dist/date.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The one post-date formatter (one site shipped two of these). Defaults to the
3
+ * long-form English style every site currently renders: "April 30, 2026".
4
+ */
5
+ export declare function formatPostDate(isoDate: string, locale?: string, options?: Intl.DateTimeFormatOptions): string;
6
+ //# sourceMappingURL=date.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date.d.ts","sourceRoot":"","sources":["../src/date.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,cAAc,CAC7B,OAAO,EAAE,MAAM,EACf,MAAM,SAAO,EACb,OAAO,GAAE,IAAI,CAAC,qBAIb,GACC,MAAM,CAER"}
package/dist/date.js ADDED
@@ -0,0 +1,12 @@
1
+ /**
2
+ * The one post-date formatter (one site shipped two of these). Defaults to the
3
+ * long-form English style every site currently renders: "April 30, 2026".
4
+ */
5
+ export function formatPostDate(isoDate, locale = "en", options = {
6
+ year: "numeric",
7
+ month: "long",
8
+ day: "numeric",
9
+ }) {
10
+ return new Intl.DateTimeFormat(locale, options).format(new Date(isoDate));
11
+ }
12
+ //# sourceMappingURL=date.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"date.js","sourceRoot":"","sources":["../src/date.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,cAAc,CAC7B,OAAe,EACf,MAAM,GAAG,IAAI,EACb,UAAsC;IACrC,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,MAAM;IACb,GAAG,EAAE,SAAS;CACd;IAED,OAAO,IAAI,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3E,CAAC"}
package/dist/http.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function toErrorMessage(error: unknown): string;
2
+ export declare function assertResponseOk(response: Response, message: string): Promise<void>;
3
+ //# sourceMappingURL=http.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAErD;AAED,wBAAsB,gBAAgB,CACrC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,MAAM,GACb,OAAO,CAAC,IAAI,CAAC,CAQf"}
package/dist/http.js ADDED
@@ -0,0 +1,10 @@
1
+ export function toErrorMessage(error) {
2
+ return error instanceof Error ? error.message : String(error);
3
+ }
4
+ export async function assertResponseOk(response, message) {
5
+ if (response.ok)
6
+ return;
7
+ const body = await response.text().catch(() => "");
8
+ throw new Error(`${message}: ${response.status} ${response.statusText}${body ? ` — ${body.slice(0, 300)}` : ""}`);
9
+ }
10
+ //# sourceMappingURL=http.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,cAAc,CAAC,KAAc;IAC5C,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACrC,QAAkB,EAClB,OAAe;IAEf,IAAI,QAAQ,CAAC,EAAE;QAAE,OAAO;IACxB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IACnD,MAAM,IAAI,KAAK,CACd,GAAG,OAAO,KAAK,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,GACpD,IAAI,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EACrC,EAAE,CACF,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ export { blogFrontmatterSchema, type BlogFrontmatter, type BlogFrontmatterInput, } from "./schema.js";
2
+ export type { BlogPost, BlogPostPreview, PostFormat } from "./types.js";
3
+ export { VOCABULARY, vocabularyProps, defineBlogComponents, calloutProps, figureProps, youTubeProps, tweetProps, newsletterSubscribeProps, type BlogComponents, type VocabularyName, type VocabularyPropsMap, type CalloutProps, type FigureProps, type YouTubeProps, type TweetProps, type NewsletterSubscribeProps, } from "./contract.js";
4
+ export { readingTime, DEFAULT_WORDS_PER_MINUTE, type ReadingTime, } from "./reading-time.js";
5
+ export { formatPostDate } from "./date.js";
6
+ export { blogPostArticle, blogPostBreadcrumbs, postUrl, type BlogSeoConfig, } from "./seo.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACN,qBAAqB,EACrB,KAAK,eAAe,EACpB,KAAK,oBAAoB,GACzB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACxE,OAAO,EACN,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,UAAU,EACV,wBAAwB,EACxB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,wBAAwB,GAC7B,MAAM,eAAe,CAAC;AACvB,OAAO,EACN,WAAW,EACX,wBAAwB,EACxB,KAAK,WAAW,GAChB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACN,eAAe,EACf,mBAAmB,EACnB,OAAO,EACP,KAAK,aAAa,GAClB,MAAM,UAAU,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // Root entry: the contract — schema, types, vocabulary manifest — plus pure
2
+ // helpers. Importable anywhere (client, admin, route handlers); no fs, no
3
+ // components, no MDX compiler. The reader/sources live at "./server", the
4
+ // renderers at "./render", the default components at "./unstyled".
5
+ export { blogFrontmatterSchema, } from "./schema.js";
6
+ export { VOCABULARY, vocabularyProps, defineBlogComponents, calloutProps, figureProps, youTubeProps, tweetProps, newsletterSubscribeProps, } from "./contract.js";
7
+ export { readingTime, DEFAULT_WORDS_PER_MINUTE, } from "./reading-time.js";
8
+ export { formatPostDate } from "./date.js";
9
+ export { blogPostArticle, blogPostBreadcrumbs, postUrl, } from "./seo.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0EAA0E;AAC1E,0EAA0E;AAC1E,mEAAmE;AACnE,OAAO,EACN,qBAAqB,GAGrB,MAAM,aAAa,CAAC;AAErB,OAAO,EACN,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,UAAU,EACV,wBAAwB,GASxB,MAAM,eAAe,CAAC;AACvB,OAAO,EACN,WAAW,EACX,wBAAwB,GAExB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EACN,eAAe,EACf,mBAAmB,EACnB,OAAO,GAEP,MAAM,UAAU,CAAC"}
package/dist/keys.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Env contract. Only the GitHub source/publisher needs anything, and callers
3
+ * may inject the token directly instead — so everything here is optional.
4
+ */
5
+ export declare const keys: () => {
6
+ GITHUB_TOKEN?: string | undefined;
7
+ };
8
+ //# sourceMappingURL=keys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,eAAO,MAAM,IAAI;;CAKI,CAAC"}
package/dist/keys.js ADDED
@@ -0,0 +1,11 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Env contract. Only the GitHub source/publisher needs anything, and callers
4
+ * may inject the token directly instead — so everything here is optional.
5
+ */
6
+ export const keys = () => z
7
+ .object({
8
+ GITHUB_TOKEN: z.string().min(1).optional(),
9
+ })
10
+ .parse(process.env);
11
+ //# sourceMappingURL=keys.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keys.js","sourceRoot":"","sources":["../src/keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,MAAM,CAAC,MAAM,IAAI,GAAG,GAAG,EAAE,CACxB,CAAC;KACC,MAAM,CAAC;IACP,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;CAC1C,CAAC;KACD,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ import type { Root } from "mdast";
2
+ import type { VFile } from "vfile";
3
+ /**
4
+ * The "limited MDX" trust boundary, enforced in the AST — not a convention.
5
+ *
6
+ * MDX is a programming language: `{process.env.X}`, `export const x = …`, and
7
+ * ESM imports all execute server-side with zero components involved. A Tier-1
8
+ * post (the only thing the automated publisher may emit as .mdx) is therefore
9
+ * restricted to prose + whitelisted JSX elements with literal attributes;
10
+ * every expression/ESM construct is rejected at compile time.
11
+ */
12
+ export interface LimitedMdxOptions {
13
+ /** Component names the post may reference (the site's vocabulary). */
14
+ allow: readonly string[];
15
+ }
16
+ /** Remark plugin implementing the boundary. Throws (via `file.fail`) on violation. */
17
+ export declare function remarkLimitedMdx(options: LimitedMdxOptions): (tree: Root, file: VFile) => void;
18
+ export interface LimitedMdxViolation {
19
+ message: string;
20
+ line?: number;
21
+ column?: number;
22
+ }
23
+ export interface LimitedMdxResult {
24
+ ok: boolean;
25
+ errors: LimitedMdxViolation[];
26
+ }
27
+ /**
28
+ * Admin's pre-publish lint: compile (without evaluating) a post source —
29
+ * frontmatter included — against a vocabulary. Also surfaces MDX syntax
30
+ * errors, so an LLM-authored post can never break a target site's build.
31
+ */
32
+ export declare function validateLimitedMdx(source: string, options: LimitedMdxOptions): Promise<LimitedMdxResult>;
33
+ //# sourceMappingURL=limited-mdx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limited-mdx.d.ts","sourceRoot":"","sources":["../src/limited-mdx.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAGlC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAGnC;;;;;;;;GAQG;AACH,MAAM,WAAW,iBAAiB;IACjC,sEAAsE;IACtE,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACzB;AAoHD,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,IAClD,MAAM,IAAI,EAAE,MAAM,KAAK,KAAG,IAAI,CAsBtC;AAED,MAAM,WAAW,mBAAmB;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAChC,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,mBAAmB,EAAE,CAAC;CAC9B;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CACvC,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,iBAAiB,GACxB,OAAO,CAAC,gBAAgB,CAAC,CAuB3B"}