@usezanin/blog 0.2.0 → 0.3.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 +17 -2
- package/dist/config.d.ts +4 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/validate.d.ts +18 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +101 -0
- package/dist/validate.js.map +1 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ npm install @usezanin/blog
|
|
|
8
8
|
|
|
9
9
|
## Setup
|
|
10
10
|
|
|
11
|
-
Env vars: `ZANIN_BASE_URL`, `ZANIN_SITE_ID`, `ZANIN_SITE_KEY` (from the Zanin dashboard), optionally `ZANIN_SITE_URL` (your site's public origin, for canonical URLs/RSS) and `ZANIN_REVALIDATE_SECRET`.
|
|
11
|
+
Env vars: `ZANIN_BASE_URL`, `ZANIN_SITE_ID`, `ZANIN_SITE_KEY` (from the Zanin dashboard), optionally `ZANIN_SITE_URL` (your site's public origin, for canonical URLs/RSS) and `ZANIN_REVALIDATE_SECRET`. Rendering only reads, so make `ZANIN_SITE_KEY` a **read-only key scoped to this site** (Settings → API keys) rather than a full-write workspace credential.
|
|
12
12
|
|
|
13
13
|
```tsx
|
|
14
14
|
// app/layout.tsx — import the themable styles once
|
|
@@ -57,6 +57,20 @@ import { createRevalidateHandler } from "@usezanin/blog";
|
|
|
57
57
|
export const POST = createRevalidateHandler();
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
### Static export (`output: "export"`)
|
|
61
|
+
|
|
62
|
+
Statically exported sites have no server at runtime, so:
|
|
63
|
+
|
|
64
|
+
```ts
|
|
65
|
+
// app/blog/rss.xml/route.ts — add force-static so the file is emitted at build time
|
|
66
|
+
import { createRssHandler } from "@usezanin/blog";
|
|
67
|
+
export const dynamic = "force-static";
|
|
68
|
+
export const GET = createRssHandler();
|
|
69
|
+
// same for app/blog/sitemap.xml/route.ts
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Skip `createRevalidateHandler` entirely — there is no ISR cache to bust. Instead point the site's revalidate webhook URL (Zanin dashboard → Settings → Sites) at a [Vercel Deploy Hook](https://vercel.com/docs/deploy-hooks) (or your CI's equivalent): publishing a post then triggers a rebuild that pulls fresh content through `generateStaticParams`. The build environment needs `ZANIN_SITE_KEY` — use a read-only site-scoped key.
|
|
73
|
+
|
|
60
74
|
## Theming — fully customizable
|
|
61
75
|
|
|
62
76
|
Blogs are themable end to end: colors, fonts, sizes, spacing, radius, content width, and index layout.
|
|
@@ -85,7 +99,8 @@ Custom fonts: load them however you like (`next/font`, `<link>` tags) and refere
|
|
|
85
99
|
|
|
86
100
|
## What you get
|
|
87
101
|
|
|
88
|
-
- Server-rendered MDX with built-in components: `Callout`, `Figure`, `Chart` (bar/line/area/pie from inline data), `CTA`, `YouTube`, `Tweet`, `ComparisonTable`, `FAQ` (
|
|
102
|
+
- Server-rendered MDX with built-in components: `Callout`, `Figure`, `Chart` (bar/line/area/pie from inline data), `CTA`, `YouTube`, `Tweet`, `ComparisonTable`, `FAQ` (takes `items={[{ q, a }]}` and emits FAQPage JSON-LD), styled code blocks — exact prop tables live in `builtinComponentDocs` (re-exported here), the `get_builtin_components` MCP tool, and the deployed `/docs` page
|
|
103
|
+
- `validateMdx(source, { knownComponents })` — the same compiler `BlogPost` uses, as a linter: MDX errors, unknown component tags, unknown/missing props on built-ins
|
|
89
104
|
- Per-site custom components: agents upload TSX to the CMS; the SDK loads the compiled result at render time — no redeploy
|
|
90
105
|
- SEO: canonical/OG/Twitter metadata, JSON-LD Article schema, static params for SSG, RSS + sitemap
|
|
91
106
|
- ISR with instant, webhook-driven revalidation on publish
|
package/dist/config.d.ts
CHANGED
|
@@ -9,7 +9,10 @@ export interface ZaninBlogConfig {
|
|
|
9
9
|
baseUrl?: string;
|
|
10
10
|
/** Site id from the Zanin dashboard. Falls back to ZANIN_SITE_ID. */
|
|
11
11
|
siteId?: string;
|
|
12
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* API key ("zn_..."). Falls back to ZANIN_SITE_KEY / ZANIN_API_KEY.
|
|
14
|
+
* Rendering only reads, so use a read-only key scoped to this site.
|
|
15
|
+
*/
|
|
13
16
|
apiKey?: string;
|
|
14
17
|
/** Path the blog is mounted at in the host site. Default "/blog". */
|
|
15
18
|
basePath?: string;
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wIAAwI;IACxI,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2EAA2E;IAC3E,UAAU,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IAC5B,kHAAkH;IAClH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,uBAAuB;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,KAAK,CAAC;IAC3B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,UAAU,CAAC;CACpB;AAED,wBAAgB,aAAa,CAAC,MAAM,GAAE,eAAoB,GAAG,uBAAuB,CAuBnF"}
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAyCA,MAAM,UAAU,aAAa,CAAC,SAA0B,EAAE;IACxD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC7D,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAC1D,MAAM,MAAM,GACV,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;IAE3E,IAAI,CAAC,OAAO;QAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;IACzF,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,KAAK,CAAC,uDAAuD,CAAC,CAAC;IACtF,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC5E,CAAC;IAED,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QACpC,MAAM;QACN,MAAM;QACN,QAAQ,EAAE,iBAAiB,CAAC,MAAM,CAAC,QAAQ,IAAI,OAAO,CAAC;QACvD,OAAO,EAAE,CAAC,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAC5E,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,UAAU,EAAE,MAAM,CAAC,UAAU,IAAI,GAAG;QACpC,gBAAgB,EAAE,MAAM,CAAC,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC,uBAAuB;QAChF,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC;AAED,SAAS,iBAAiB,CAAC,IAAY;IACrC,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;IAC7D,OAAO,WAAW,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC;AAChD,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { indexLayoutClass, mergeThemes, themeToStyle, type ZaninTheme } from "./
|
|
|
7
7
|
export { createRevalidateHandler, createRssHandler, createSitemapHandler, } from "./handlers.js";
|
|
8
8
|
export { fetchCustomComponents } from "./custom-components.js";
|
|
9
9
|
export { builtinComponents, renderMdx } from "./mdx.js";
|
|
10
|
+
export { validateMdx, type MdxIssue, type MdxValidation, type ValidateMdxOptions, } from "./validate.js";
|
|
11
|
+
export { builtinComponentDocs, builtinComponentNames, type BuiltinComponentDoc, type BuiltinComponentProp, } from "@usezanin/client";
|
|
10
12
|
export { generateBlogMetadata, generateBlogStaticParams } from "./seo.js";
|
|
11
13
|
export { Callout } from "./components/Callout.js";
|
|
12
14
|
export { Chart, type ChartProps } from "./components/Chart.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC1F,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,YAAY,CAAC;AAC1F,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EACL,WAAW,EACX,KAAK,QAAQ,EACb,KAAK,aAAa,EAClB,KAAK,kBAAkB,GACxB,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,KAAK,mBAAmB,EACxB,KAAK,oBAAoB,GAC1B,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,GAAG,EAAE,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,8 @@ export { indexLayoutClass, mergeThemes, themeToStyle } from "./theme.js";
|
|
|
7
7
|
export { createRevalidateHandler, createRssHandler, createSitemapHandler, } from "./handlers.js";
|
|
8
8
|
export { fetchCustomComponents } from "./custom-components.js";
|
|
9
9
|
export { builtinComponents, renderMdx } from "./mdx.js";
|
|
10
|
+
export { validateMdx, } from "./validate.js";
|
|
11
|
+
export { builtinComponentDocs, builtinComponentNames, } from "@usezanin/client";
|
|
10
12
|
export { generateBlogMetadata, generateBlogStaticParams } from "./seo.js";
|
|
11
13
|
export { Callout } from "./components/Callout.js";
|
|
12
14
|
export { Chart } from "./components/Chart.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAuB,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAsB,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAmB,MAAM,YAAY,CAAC;AAC1F,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAmB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,GAAG,EAAgB,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAuB,MAAM,gBAAgB,CAAC;AAC1E,OAAO,EAAE,QAAQ,EAAsB,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAwB,MAAM,aAAa,CAAC;AAClE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,WAAW,EAAE,YAAY,EAAmB,MAAM,YAAY,CAAC;AAC1F,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,oBAAoB,GACrB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EACL,WAAW,GAIZ,MAAM,eAAe,CAAC;AACvB,OAAO,EACL,oBAAoB,EACpB,qBAAqB,GAGtB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,oBAAoB,EAAE,wBAAwB,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,KAAK,EAAmB,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,GAAG,EAAE,MAAM,qBAAqB,CAAC;AAC1C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EAAE,GAAG,EAAgB,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type MdxIssue, type MdxValidation } from "@usezanin/client";
|
|
2
|
+
export type { MdxIssue, MdxValidation };
|
|
3
|
+
export interface ValidateMdxOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Component tags available besides the built-ins (per-site custom
|
|
6
|
+
* components and/or host-provided components).
|
|
7
|
+
*/
|
|
8
|
+
knownComponents?: string[];
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Validates an MDX post body with the exact pipeline the SDK uses to render
|
|
12
|
+
* it (@mdx-js/mdx + remark-gfm), then lints component usage:
|
|
13
|
+
*
|
|
14
|
+
* - errors: MDX syntax/compile failures, missing required props on built-ins
|
|
15
|
+
* - warnings: unknown component tags, unknown props on built-in components
|
|
16
|
+
*/
|
|
17
|
+
export declare function validateMdx(source: string, options?: ValidateMdxOptions): Promise<MdxValidation>;
|
|
18
|
+
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAG1B,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;AAwBxC,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,aAAa,CAAC,CAwFxB"}
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { compile } from "@mdx-js/mdx";
|
|
2
|
+
import { builtinComponentDocs, builtinComponentNames, } from "@usezanin/client";
|
|
3
|
+
import remarkGfm from "remark-gfm";
|
|
4
|
+
function collectJsxElements(tree, out) {
|
|
5
|
+
if (tree.type === "mdxJsxFlowElement" || tree.type === "mdxJsxTextElement") {
|
|
6
|
+
out.push(tree);
|
|
7
|
+
}
|
|
8
|
+
for (const child of tree.children ?? []) {
|
|
9
|
+
collectJsxElements(child, out);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Validates an MDX post body with the exact pipeline the SDK uses to render
|
|
14
|
+
* it (@mdx-js/mdx + remark-gfm), then lints component usage:
|
|
15
|
+
*
|
|
16
|
+
* - errors: MDX syntax/compile failures, missing required props on built-ins
|
|
17
|
+
* - warnings: unknown component tags, unknown props on built-in components
|
|
18
|
+
*/
|
|
19
|
+
export async function validateMdx(source, options = {}) {
|
|
20
|
+
const errors = [];
|
|
21
|
+
const warnings = [];
|
|
22
|
+
const known = new Set([...builtinComponentNames, ...(options.knownComponents ?? [])]);
|
|
23
|
+
const elements = [];
|
|
24
|
+
const collector = () => (tree) => {
|
|
25
|
+
collectJsxElements(tree, elements);
|
|
26
|
+
};
|
|
27
|
+
try {
|
|
28
|
+
await compile(source, { remarkPlugins: [remarkGfm, collector] });
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
const message = err;
|
|
32
|
+
errors.push({
|
|
33
|
+
severity: "error",
|
|
34
|
+
rule: "mdx-syntax",
|
|
35
|
+
message: message.reason ?? message.message ?? String(err),
|
|
36
|
+
...(typeof message.line === "number" ? { line: message.line } : {}),
|
|
37
|
+
...(typeof message.column === "number" ? { column: message.column } : {}),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
for (const el of elements) {
|
|
41
|
+
const name = el.name;
|
|
42
|
+
// Skip fragments, lowercase HTML tags and member expressions (Foo.Bar).
|
|
43
|
+
if (!name || !/^[A-Z]/.test(name) || name.includes("."))
|
|
44
|
+
continue;
|
|
45
|
+
const position = {
|
|
46
|
+
...(el.position?.start?.line !== undefined ? { line: el.position.start.line } : {}),
|
|
47
|
+
...(el.position?.start?.column !== undefined ? { column: el.position.start.column } : {}),
|
|
48
|
+
};
|
|
49
|
+
if (!known.has(name)) {
|
|
50
|
+
warnings.push({
|
|
51
|
+
severity: "warning",
|
|
52
|
+
rule: "unknown-component",
|
|
53
|
+
component: name,
|
|
54
|
+
message: `<${name}> is not a built-in component and no custom component with that name exists on this site. The post will fail to render unless the host site supplies it via the components prop. Built-ins: ${builtinComponentNames.join(", ")}.`,
|
|
55
|
+
...position,
|
|
56
|
+
});
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const doc = builtinComponentDocs.find((d) => d.name === name);
|
|
60
|
+
if (!doc)
|
|
61
|
+
continue; // custom component — no prop metadata to lint against
|
|
62
|
+
const attrs = el.attributes ?? [];
|
|
63
|
+
// A spread ({...props}) can supply anything; skip prop-level checks.
|
|
64
|
+
const hasSpread = attrs.some((a) => a.type === "mdxJsxExpressionAttribute");
|
|
65
|
+
const attrNames = attrs
|
|
66
|
+
.filter((a) => a.type === "mdxJsxAttribute" && typeof a.name === "string")
|
|
67
|
+
.map((a) => a.name);
|
|
68
|
+
const validProps = new Set(doc.props.map((p) => p.name));
|
|
69
|
+
for (const attr of attrNames) {
|
|
70
|
+
if (!validProps.has(attr)) {
|
|
71
|
+
warnings.push({
|
|
72
|
+
severity: "warning",
|
|
73
|
+
rule: "unknown-prop",
|
|
74
|
+
component: name,
|
|
75
|
+
message: `<${name}> has no "${attr}" prop. Valid props: ${doc.props.map((p) => p.name).join(", ") || "(none)"}.`,
|
|
76
|
+
...position,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (!hasSpread) {
|
|
81
|
+
for (const prop of doc.props) {
|
|
82
|
+
if (prop.required && !attrNames.includes(prop.name)) {
|
|
83
|
+
errors.push({
|
|
84
|
+
severity: "error",
|
|
85
|
+
rule: "missing-required-prop",
|
|
86
|
+
component: name,
|
|
87
|
+
message: `<${name}> is missing its required "${prop.name}" prop (${prop.type}) and will fail to render.`,
|
|
88
|
+
...position,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
valid: errors.length === 0,
|
|
96
|
+
errors,
|
|
97
|
+
warnings,
|
|
98
|
+
knownComponents: [...known].sort(),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../src/validate.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usezanin/blog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "React SDK for rendering Zanin blogs in Next.js sites \u2014 index/post components, built-in MDX components, SEO, RSS and sitemap",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mdx-js/mdx": "^3.1.0",
|
|
44
|
-
"@usezanin/client": "^0.
|
|
44
|
+
"@usezanin/client": "^0.3.0",
|
|
45
45
|
"recharts": "^2.15.0",
|
|
46
46
|
"remark-gfm": "^4.0.0"
|
|
47
47
|
},
|