@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
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
|
package/dist/blog.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blog.js","sourceRoot":"","sources":["../src/blog.ts"],"names":[],"mappings":"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"}
|
|
@@ -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"}
|
package/dist/contract.js
ADDED
|
@@ -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
|
package/dist/date.js.map
ADDED
|
@@ -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 @@
|
|
|
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
|
package/dist/http.js.map
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
package/dist/keys.js.map
ADDED
|
@@ -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"}
|