@binarynoir/vitepress-blog 0.0.0-stage → 0.1.1

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.
@@ -0,0 +1,234 @@
1
+ import {
2
+ blogMarkdown,
3
+ findBlogsListing,
4
+ fingerprintBlogSource,
5
+ hasWidget,
6
+ isIndex,
7
+ isPost,
8
+ joinUrl,
9
+ listBlogPosts,
10
+ loadBlogPages,
11
+ mayHaveBlogFrontmatter,
12
+ renderFeed,
13
+ resolvePostImage,
14
+ toFeedPath,
15
+ toPageUrl,
16
+ widgetPages
17
+ } from "./chunk-DICBXOTS.js";
18
+
19
+ // src/vitepress.ts
20
+ import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from "fs";
21
+ import path from "path";
22
+ var DEFAULT_FEED_LIMIT = 20;
23
+ function resolveSiteLocation(siteConfig, options) {
24
+ const siteUrl = options.siteUrl ?? siteConfig.userConfig?.sitemap?.hostname ?? siteConfig.sitemap?.hostname;
25
+ if (!siteUrl) return null;
26
+ return {
27
+ siteUrl: siteUrl.replace(/\/+$/, ""),
28
+ base: siteConfig.site?.base ?? "/",
29
+ cleanUrls: !!siteConfig.cleanUrls,
30
+ publicDir: siteConfig.publicDir ?? path.join(siteConfig.srcDir, "public"),
31
+ imagesDir: (options.imagesDir ?? "blog-images").replace(/^\/+|\/+$/g, "")
32
+ };
33
+ }
34
+ function loadPages(siteConfig, options) {
35
+ return loadBlogPages(siteConfig.srcDir, options, siteConfig);
36
+ }
37
+ function listFeedPosts(pages, index, options) {
38
+ const limit = index.index.feed?.limit ?? options.feedLimit ?? DEFAULT_FEED_LIMIT;
39
+ return listBlogPosts(pages, index, { drafts: false, stickyFirst: false, applyLimit: false }).slice(0, limit);
40
+ }
41
+ function renderIndexFeed(pages, index, site, siteConfig, options) {
42
+ return renderFeed({
43
+ index,
44
+ posts: listFeedPosts(pages, index, options),
45
+ site,
46
+ language: siteConfig.site?.lang,
47
+ siteDescription: siteConfig.site?.description
48
+ });
49
+ }
50
+ function feedLink(index, base) {
51
+ const href = joinUrl(base, toFeedPath(index));
52
+ return [
53
+ "link",
54
+ { rel: "alternate", type: "application/rss+xml", title: index.index.feed?.title ?? index.title, href }
55
+ ];
56
+ }
57
+ function blogHead(context, options = {}) {
58
+ const { pageData, siteConfig } = context;
59
+ const frontmatter = pageData.frontmatter ?? {};
60
+ if (!pageData.filePath || !frontmatter.blog && !frontmatter["blog-index"]) return [];
61
+ const pages = loadPages(siteConfig, options);
62
+ const page = pages.find((candidate) => candidate.relativePath === pageData.filePath);
63
+ if (!page) return [];
64
+ const site = resolveSiteLocation(siteConfig, options);
65
+ const base = siteConfig.site?.base ?? "/";
66
+ const head = [];
67
+ if (isIndex(page)) {
68
+ if (page.index.feed) head.push(feedLink(page, base));
69
+ if (site) head.push(["meta", { property: "og:url", content: toPageUrl(page.routePath, site) }]);
70
+ return head;
71
+ }
72
+ if (!isPost(page)) return head;
73
+ const { post } = page;
74
+ head.push(
75
+ ["meta", { property: "og:type", content: "article" }],
76
+ ["meta", { property: "og:title", content: post.title }]
77
+ );
78
+ if (post.summary) head.push(["meta", { property: "og:description", content: post.summary }]);
79
+ if (site) head.push(["meta", { property: "og:url", content: toPageUrl(page.routePath, site) }]);
80
+ const image = site ? resolvePostImage(page, site) : null;
81
+ if (image) {
82
+ head.push(["meta", { property: "og:image", content: image.url }]);
83
+ if (post.imageAlt) head.push(["meta", { property: "og:image:alt", content: post.imageAlt }]);
84
+ }
85
+ head.push(["meta", { name: "twitter:card", content: image ? "summary_large_image" : "summary" }]);
86
+ if (post.date) head.push(["meta", { property: "article:published_time", content: post.date.toISOString() }]);
87
+ if (post.updated) head.push(["meta", { property: "article:modified_time", content: post.updated.toISOString() }]);
88
+ if (post.authors.length) {
89
+ head.push(["meta", { property: "article:author", content: post.authors.map((author) => author.name).join(", ") }]);
90
+ }
91
+ if (post.category) head.push(["meta", { property: "article:section", content: post.category }]);
92
+ if (!post.draft) {
93
+ for (const blog of findBlogsListing(pages, page)) if (blog.index.feed) head.push(feedLink(blog, base));
94
+ }
95
+ return head;
96
+ }
97
+ function blogPageData(pageData, context, options = {}) {
98
+ if (!pageData.frontmatter?.blog || pageData.description || !pageData.filePath) return;
99
+ const page = loadPages(context.siteConfig, options).find((candidate) => candidate.relativePath === pageData.filePath);
100
+ if (page?.post?.summary) pageData.description = page.post.summary;
101
+ }
102
+ function writeBlogFeeds(siteConfig, options = {}) {
103
+ const pages = loadPages(siteConfig, options);
104
+ const indexes = pages.filter(isIndex).filter((index) => index.index.feed);
105
+ const site = resolveSiteLocation(siteConfig, options);
106
+ if (!site) {
107
+ if (indexes.length) {
108
+ siteConfig.logger?.warn(
109
+ "[vitepress-blog] no feeds written: set the siteUrl option or sitemap.hostname so feeds can use absolute links."
110
+ );
111
+ }
112
+ return;
113
+ }
114
+ const written = /* @__PURE__ */ new Map();
115
+ for (const index of indexes) {
116
+ const feedPath = toFeedPath(index);
117
+ if (written.has(feedPath)) {
118
+ siteConfig.logger?.warn(
119
+ `[vitepress-blog] ${index.relativePath} and ${written.get(feedPath)} both write ${feedPath}; set feed.path on one of them.`
120
+ );
121
+ continue;
122
+ }
123
+ written.set(feedPath, index.relativePath);
124
+ const file = path.join(siteConfig.outDir, feedPath);
125
+ mkdirSync(path.dirname(file), { recursive: true });
126
+ writeFileSync(file, renderIndexFeed(pages, index, site, siteConfig, options));
127
+ }
128
+ for (const post of pages.filter(isPost)) {
129
+ if (post.post.draft) continue;
130
+ const image = resolvePostImage(post, site);
131
+ if (!image?.file || !image.outputPath) continue;
132
+ const target = path.join(siteConfig.outDir, image.outputPath);
133
+ mkdirSync(path.dirname(target), { recursive: true });
134
+ copyFileSync(image.file, target);
135
+ }
136
+ }
137
+ function blogVitePlugin(options = {}) {
138
+ let siteConfig;
139
+ const currentPages = () => siteConfig ? loadPages(siteConfig, options) : [];
140
+ const dependentFiles = () => [...currentPages().map((page) => page.absolutePath), ...widgetPages];
141
+ return {
142
+ name: "vitepress-blog",
143
+ enforce: "pre",
144
+ configResolved(config) {
145
+ siteConfig = config.vitepress;
146
+ },
147
+ transform(code, id) {
148
+ if (!siteConfig || !id.endsWith(".md") || !(mayHaveBlogFrontmatter(code) || hasWidget(code))) return null;
149
+ return { code: `${code}
150
+ <!-- vitepress-blog:${fingerprintBlogSource(currentPages())} -->
151
+ `, map: null };
152
+ },
153
+ configureServer(server) {
154
+ const refresh = (file) => {
155
+ if (!siteConfig || !file.endsWith(".md")) return;
156
+ for (const dependent of dependentFiles()) {
157
+ for (const module of server.moduleGraph.getModulesByFile(dependent) ?? []) {
158
+ server.moduleGraph.invalidateModule(module);
159
+ }
160
+ }
161
+ server.ws.send({ type: "full-reload" });
162
+ };
163
+ server.watcher.on("add", refresh).on("unlink", refresh);
164
+ server.middlewares.use((request, response, next) => {
165
+ if (!siteConfig || !request.url) return next();
166
+ const base = siteConfig.site?.base ?? "/";
167
+ const pathname = decodeURIComponent(request.url.split("?")[0]);
168
+ if (!pathname.startsWith(base)) return next();
169
+ const relative = pathname.slice(base.length);
170
+ const pages = currentPages();
171
+ const site = resolveSiteLocation(siteConfig, options) ?? resolveSiteLocation(siteConfig, { ...options, siteUrl: `http://${request.headers.host ?? "localhost"}` });
172
+ const index = pages.filter(isIndex).find((page) => page.index.feed && toFeedPath(page) === relative);
173
+ if (index) {
174
+ response.setHeader("Content-Type", "application/rss+xml; charset=utf-8");
175
+ response.end(renderIndexFeed(pages, index, site, siteConfig, options));
176
+ return;
177
+ }
178
+ for (const post of pages.filter(isPost)) {
179
+ const image = resolvePostImage(post, site);
180
+ if (image?.outputPath === relative && image.file) {
181
+ if (image.type) response.setHeader("Content-Type", image.type);
182
+ response.end(readFileSync(image.file));
183
+ return;
184
+ }
185
+ }
186
+ next();
187
+ });
188
+ },
189
+ handleHotUpdate({ file, modules, server }) {
190
+ if (!siteConfig || !file.endsWith(".md")) return;
191
+ const pages = currentPages();
192
+ if (!pages.some((page) => page.absolutePath === file)) return;
193
+ const affected = dependentFiles().filter((dependent) => dependent !== file).flatMap((dependent) => [...server.moduleGraph.getModulesByFile(dependent) ?? []]);
194
+ return affected.length ? [...modules, ...affected] : void 0;
195
+ }
196
+ };
197
+ }
198
+ function withBlog(config, options = {}) {
199
+ config.markdown ??= {};
200
+ const existingMarkdownConfig = config.markdown.config ?? (() => {
201
+ });
202
+ config.markdown.config = (md) => {
203
+ blogMarkdown(md, options);
204
+ existingMarkdownConfig(md);
205
+ };
206
+ config.vite ??= {};
207
+ config.vite.plugins = [...config.vite.plugins ?? [], blogVitePlugin(options)];
208
+ const existingTransformHead = config.transformHead;
209
+ config.transformHead = async (context) => [
210
+ ...blogHead(context, options),
211
+ ...await existingTransformHead?.(context) ?? []
212
+ ];
213
+ const existingTransformPageData = config.transformPageData;
214
+ config.transformPageData = (pageData, context) => {
215
+ blogPageData(pageData, context, options);
216
+ return existingTransformPageData?.(pageData, context);
217
+ };
218
+ const existingBuildEnd = config.buildEnd;
219
+ config.buildEnd = async (siteConfig) => {
220
+ writeBlogFeeds(siteConfig, options);
221
+ await existingBuildEnd?.(siteConfig);
222
+ };
223
+ return config;
224
+ }
225
+ export {
226
+ blogHead,
227
+ blogMarkdown,
228
+ blogPageData,
229
+ blogVitePlugin,
230
+ resolveSiteLocation,
231
+ withBlog,
232
+ writeBlogFeeds
233
+ };
234
+ //# sourceMappingURL=vitepress.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/vitepress.ts"],"sourcesContent":["import { copyFileSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport path from 'node:path';\nimport type { MarkdownIt } from 'markdown-it';\nimport type { Plugin } from 'vite';\nimport type { HeadConfig, PageData, SiteConfig, TransformContext, TransformPageContext, UserConfig } from 'vitepress';\nimport { findBlogsListing, isIndex, isPost, listBlogPosts, type IndexPage, type PostPage } from './collect.js';\nimport { mayHaveBlogFrontmatter } from './frontmatter.js';\nimport { renderFeed, resolvePostImage, toFeedPath, toPageUrl, type SiteLocation } from './feed.js';\nimport { blogMarkdown } from './plugin.js';\nimport { hasWidget, widgetPages } from './widget.js';\nimport { fingerprintBlogSource, loadBlogPages, type BlogPage } from './scan.js';\nimport type { BlogOptions } from './types.js';\nimport { joinUrl } from './util.js';\n\nconst DEFAULT_FEED_LIMIT = 20;\n\n/** Where the site is published, or `null` without a site URL: feeds need absolute links. */\nexport function resolveSiteLocation(siteConfig: SiteConfig, options: BlogOptions): SiteLocation | null {\n const siteUrl = options.siteUrl ?? siteConfig.userConfig?.sitemap?.hostname ?? siteConfig.sitemap?.hostname;\n if (!siteUrl) return null;\n return {\n siteUrl: siteUrl.replace(/\\/+$/, ''),\n base: siteConfig.site?.base ?? '/',\n cleanUrls: !!siteConfig.cleanUrls,\n publicDir: siteConfig.publicDir ?? path.join(siteConfig.srcDir, 'public'),\n imagesDir: (options.imagesDir ?? 'blog-images').replace(/^\\/+|\\/+$/g, ''),\n };\n}\n\nfunction loadPages(siteConfig: SiteConfig, options: BlogOptions): BlogPage[] {\n return loadBlogPages(siteConfig.srcDir, options, siteConfig);\n}\n\n/** The posts in a blog's feed: no drafts, newest first, up to the feed's limit. */\nfunction listFeedPosts(pages: BlogPage[], index: IndexPage, options: BlogOptions): PostPage[] {\n const limit = index.index.feed?.limit ?? options.feedLimit ?? DEFAULT_FEED_LIMIT;\n return listBlogPosts(pages, index, { drafts: false, stickyFirst: false, applyLimit: false }).slice(0, limit);\n}\n\nfunction renderIndexFeed(\n pages: BlogPage[],\n index: IndexPage,\n site: SiteLocation,\n siteConfig: SiteConfig,\n options: BlogOptions,\n) {\n return renderFeed({\n index,\n posts: listFeedPosts(pages, index, options),\n site,\n language: siteConfig.site?.lang,\n siteDescription: siteConfig.site?.description,\n });\n}\n\nfunction feedLink(index: IndexPage, base: string): HeadConfig {\n const href = joinUrl(base, toFeedPath(index));\n return [\n 'link',\n { rel: 'alternate', type: 'application/rss+xml', title: index.index.feed?.title ?? index.title, href },\n ];\n}\n\n/**\n * `<head>` tags for blog pages: a feed link on each blog page, and on each\n * post Open Graph and Twitter card tags (title, summary, image, dates) plus\n * links to the feeds that carry it. Use it from VitePress's `transformHead`:\n *\n * ```ts\n * transformHead: (context) => blogHead(context, options),\n * ```\n *\n * Social tags need absolute URLs, so `og:url` and `og:image` are left out\n * without a site URL (the `siteUrl` option or `sitemap.hostname`).\n */\nexport function blogHead(context: TransformContext, options: BlogOptions = {}): HeadConfig[] {\n const { pageData, siteConfig } = context;\n const frontmatter = pageData.frontmatter ?? {};\n if (!pageData.filePath || (!frontmatter.blog && !frontmatter['blog-index'])) return [];\n const pages = loadPages(siteConfig, options);\n const page = pages.find((candidate) => candidate.relativePath === pageData.filePath);\n if (!page) return [];\n const site = resolveSiteLocation(siteConfig, options);\n const base = siteConfig.site?.base ?? '/';\n const head: HeadConfig[] = [];\n\n if (isIndex(page)) {\n if (page.index.feed) head.push(feedLink(page, base));\n if (site) head.push(['meta', { property: 'og:url', content: toPageUrl(page.routePath, site) }]);\n return head;\n }\n if (!isPost(page)) return head;\n\n const { post } = page;\n head.push(\n ['meta', { property: 'og:type', content: 'article' }],\n ['meta', { property: 'og:title', content: post.title }],\n );\n if (post.summary) head.push(['meta', { property: 'og:description', content: post.summary }]);\n if (site) head.push(['meta', { property: 'og:url', content: toPageUrl(page.routePath, site) }]);\n\n const image = site ? resolvePostImage(page, site) : null;\n if (image) {\n head.push(['meta', { property: 'og:image', content: image.url }]);\n if (post.imageAlt) head.push(['meta', { property: 'og:image:alt', content: post.imageAlt }]);\n }\n head.push(['meta', { name: 'twitter:card', content: image ? 'summary_large_image' : 'summary' }]);\n if (post.date) head.push(['meta', { property: 'article:published_time', content: post.date.toISOString() }]);\n if (post.updated) head.push(['meta', { property: 'article:modified_time', content: post.updated.toISOString() }]);\n if (post.authors.length) {\n head.push(['meta', { property: 'article:author', content: post.authors.map((author) => author.name).join(', ') }]);\n }\n if (post.category) head.push(['meta', { property: 'article:section', content: post.category }]);\n if (!post.draft) {\n for (const blog of findBlogsListing(pages, page)) if (blog.index.feed) head.push(feedLink(blog, base));\n }\n return head;\n}\n\n/**\n * Gives a post without a `description` its summary (or the first paragraph,\n * when it has no summary) as one, so VitePress writes it into the page's\n * `<meta name=\"description\">`. Use it from `transformPageData`:\n *\n * ```ts\n * transformPageData: (pageData, context) => blogPageData(pageData, context, options),\n * ```\n */\nexport function blogPageData(pageData: PageData, context: TransformPageContext, options: BlogOptions = {}): void {\n if (!pageData.frontmatter?.blog || pageData.description || !pageData.filePath) return;\n const page = loadPages(context.siteConfig, options).find((candidate) => candidate.relativePath === pageData.filePath);\n if (page?.post?.summary) pageData.description = page.post.summary;\n}\n\n/**\n * Writes an RSS feed for every blog page whose feed isn't turned off, and\n * copies post images to `imagesDir` for the feeds and social cards to link.\n * Use it from VitePress's `buildEnd`.\n */\nexport function writeBlogFeeds(siteConfig: SiteConfig, options: BlogOptions = {}): void {\n const pages = loadPages(siteConfig, options);\n const indexes = pages.filter(isIndex).filter((index) => index.index.feed);\n const site = resolveSiteLocation(siteConfig, options);\n if (!site) {\n if (indexes.length) {\n siteConfig.logger?.warn(\n '[vitepress-blog] no feeds written: set the siteUrl option or sitemap.hostname so feeds can use absolute links.',\n );\n }\n return;\n }\n\n const written = new Map<string, string>();\n for (const index of indexes) {\n const feedPath = toFeedPath(index);\n if (written.has(feedPath)) {\n siteConfig.logger?.warn(\n `[vitepress-blog] ${index.relativePath} and ${written.get(feedPath)} both write ${feedPath}; set feed.path on one of them.`,\n );\n continue;\n }\n written.set(feedPath, index.relativePath);\n const file = path.join(siteConfig.outDir, feedPath);\n mkdirSync(path.dirname(file), { recursive: true });\n writeFileSync(file, renderIndexFeed(pages, index, site, siteConfig, options));\n }\n\n for (const post of pages.filter(isPost)) {\n if (post.post.draft) continue;\n const image = resolvePostImage(post, site);\n if (!image?.file || !image.outputPath) continue;\n const target = path.join(siteConfig.outDir, image.outputPath);\n mkdirSync(path.dirname(target), { recursive: true });\n copyFileSync(image.file, target);\n }\n}\n\n/**\n * The Vite side of the blog, for `vitepress dev`:\n *\n * - VitePress caches a page's rendered output by its own source, so a new or\n * edited post would leave blog lists and \"newer / older\" links stale. This\n * adds a fingerprint of every post to blog pages and posts (busting the\n * cache) and reloads them when any of them changes.\n * - Serves each blog's feed and the copied post images, which the build\n * writes in `buildEnd`, so feed links work in development too.\n */\nexport function blogVitePlugin(options: BlogOptions = {}): Plugin {\n let siteConfig: SiteConfig | undefined;\n const currentPages = () => (siteConfig ? loadPages(siteConfig, options) : []);\n // Every file whose output shows posts: blog pages, posts, and pages with a `::: blog-posts` block.\n const dependentFiles = () => [...currentPages().map((page) => page.absolutePath), ...widgetPages];\n\n return {\n name: 'vitepress-blog',\n enforce: 'pre',\n configResolved(config) {\n siteConfig = (config as { vitepress?: SiteConfig }).vitepress;\n },\n transform(code, id) {\n if (!siteConfig || !id.endsWith('.md') || !(mayHaveBlogFrontmatter(code) || hasWidget(code))) return null;\n return { code: `${code}\\n<!-- vitepress-blog:${fingerprintBlogSource(currentPages())} -->\\n`, map: null };\n },\n configureServer(server) {\n // Vite only calls handleHotUpdate for edits; a post added or deleted changes the lists too.\n const refresh = (file: string) => {\n if (!siteConfig || !file.endsWith('.md')) return;\n for (const dependent of dependentFiles()) {\n for (const module of server.moduleGraph.getModulesByFile(dependent) ?? []) {\n server.moduleGraph.invalidateModule(module);\n }\n }\n server.ws.send({ type: 'full-reload' });\n };\n server.watcher.on('add', refresh).on('unlink', refresh);\n\n server.middlewares.use((request, response, next) => {\n if (!siteConfig || !request.url) return next();\n const base = siteConfig.site?.base ?? '/';\n const pathname = decodeURIComponent(request.url.split('?')[0]);\n if (!pathname.startsWith(base)) return next();\n const relative = pathname.slice(base.length);\n const pages = currentPages();\n // Feeds link to the live site, or to the dev server without a site URL.\n const site =\n resolveSiteLocation(siteConfig, options) ??\n resolveSiteLocation(siteConfig, { ...options, siteUrl: `http://${request.headers.host ?? 'localhost'}` })!;\n\n const index = pages.filter(isIndex).find((page) => page.index.feed && toFeedPath(page) === relative);\n if (index) {\n response.setHeader('Content-Type', 'application/rss+xml; charset=utf-8');\n response.end(renderIndexFeed(pages, index, site, siteConfig, options));\n return;\n }\n for (const post of pages.filter(isPost)) {\n const image = resolvePostImage(post, site);\n if (image?.outputPath === relative && image.file) {\n if (image.type) response.setHeader('Content-Type', image.type);\n response.end(readFileSync(image.file));\n return;\n }\n }\n next();\n });\n },\n handleHotUpdate({ file, modules, server }) {\n if (!siteConfig || !file.endsWith('.md')) return;\n const pages = currentPages();\n if (!pages.some((page) => page.absolutePath === file)) return;\n const affected = dependentFiles()\n .filter((dependent) => dependent !== file)\n .flatMap((dependent) => [...(server.moduleGraph.getModulesByFile(dependent) ?? [])]);\n return affected.length ? [...modules, ...affected] : undefined;\n },\n };\n}\n\n/**\n * Wraps a VitePress config with the whole blog: the Markdown plugin, the dev\n * server plugin, `<head>` tags, descriptions from summaries, and feeds at the\n * end of the build.\n *\n * ```ts\n * // .vitepress/config.mts\n * import { defineConfig } from 'vitepress';\n * import { withBlog } from '@binarynoir/vitepress-blog/vitepress';\n *\n * export default withBlog(defineConfig({ /* ...your config... *\\/ }), { siteUrl: 'https://example.com' });\n * ```\n *\n * Hooks you already have (`markdown.config`, `transformHead`,\n * `transformPageData`, `buildEnd`) are not replaced: the blog's runs and\n * then yours, so it composes with other `withX()` wrappers in any order.\n */\nexport function withBlog(config: UserConfig, options: BlogOptions = {}): UserConfig {\n config.markdown ??= {};\n const existingMarkdownConfig = config.markdown.config ?? (() => {});\n config.markdown.config = (md) => {\n // VitePress 2 types this callback against `markdown-it-async`'s MarkdownIt,\n // a structural superset of plain markdown-it's; the cast bridges the two.\n blogMarkdown(md as unknown as MarkdownIt, options);\n existingMarkdownConfig(md);\n };\n\n config.vite ??= {};\n config.vite.plugins = [...(config.vite.plugins ?? []), blogVitePlugin(options)];\n\n const existingTransformHead = config.transformHead;\n config.transformHead = async (context) => [\n ...blogHead(context, options),\n ...((await existingTransformHead?.(context)) ?? []),\n ];\n\n const existingTransformPageData = config.transformPageData;\n config.transformPageData = (pageData, context) => {\n blogPageData(pageData, context, options);\n return existingTransformPageData?.(pageData, context);\n };\n\n const existingBuildEnd = config.buildEnd;\n config.buildEnd = async (siteConfig) => {\n writeBlogFeeds(siteConfig, options);\n await existingBuildEnd?.(siteConfig);\n };\n\n return config;\n}\n\nexport { blogMarkdown };\nexport type { BlogOptions };\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,110 @@
1
1
  {
2
2
  "name": "@binarynoir/vitepress-blog",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.1",
4
+ "description": "Turn any VitePress 2 page into a blog post with frontmatter. A main blog, folder-scoped section blogs, sticky posts, pagination, keyword filters and an RSS feed for each.",
5
+ "keywords": [
6
+ "vitepress",
7
+ "vitepress-plugin",
8
+ "markdown-it",
9
+ "markdown-it-plugin",
10
+ "blog",
11
+ "rss",
12
+ "rss-feed",
13
+ "feed",
14
+ "pagination",
15
+ "tags",
16
+ "docs",
17
+ "documentation"
18
+ ],
19
+ "license": "MIT",
20
+ "author": "BinaryNoir",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/binarynoir/vitepress-blog.git"
24
+ },
25
+ "homepage": "https://github.com/binarynoir/vitepress-blog#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/binarynoir/vitepress-blog/issues"
28
+ },
29
+ "type": "module",
30
+ "sideEffects": [
31
+ "./dist/style.css"
32
+ ],
33
+ "main": "./dist/index.cjs",
34
+ "module": "./dist/index.js",
35
+ "types": "./dist/index.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "import": "./dist/index.js",
40
+ "require": "./dist/index.cjs"
41
+ },
42
+ "./vitepress": {
43
+ "types": "./dist/vitepress.d.ts",
44
+ "import": "./dist/vitepress.js",
45
+ "require": "./dist/vitepress.cjs"
46
+ },
47
+ "./client": {
48
+ "types": "./dist/client.d.ts",
49
+ "import": "./dist/client.js",
50
+ "require": "./dist/client.cjs"
51
+ },
52
+ "./style.css": "./dist/style.css",
53
+ "./package.json": "./package.json"
54
+ },
55
+ "files": [
56
+ "dist"
57
+ ],
58
+ "publishConfig": {
59
+ "access": "public"
60
+ },
61
+ "engines": {
62
+ "node": ">=22"
63
+ },
64
+ "scripts": {
65
+ "build": "tsup",
66
+ "dev": "tsup --watch",
67
+ "test": "vitest run",
68
+ "test:watch": "vitest",
69
+ "typecheck": "tsc --noEmit",
70
+ "lint": "eslint .",
71
+ "format": "prettier --write .",
72
+ "format:check": "prettier --check .",
73
+ "prepublishOnly": "npm run typecheck && npm run test && npm run build",
74
+ "release": "node scripts/release.js",
75
+ "release:patch": "node scripts/release.js patch",
76
+ "release:minor": "node scripts/release.js minor",
77
+ "release:major": "node scripts/release.js major"
78
+ },
79
+ "dependencies": {
80
+ "yaml": "^2.9.1"
81
+ },
82
+ "peerDependencies": {
83
+ "markdown-it": "^15.0.0",
84
+ "vitepress": "^1.0.0 || ^2.0.0-0",
85
+ "vue": "^3.4.0"
86
+ },
87
+ "peerDependenciesMeta": {
88
+ "vitepress": {
89
+ "optional": true
90
+ },
91
+ "vue": {
92
+ "optional": true
93
+ }
94
+ },
95
+ "devDependencies": {
96
+ "@types/node": "^26.6.4",
97
+ "eslint": "^10.11.0",
98
+ "eslint-config-prettier": "^10.1.8",
99
+ "happy-dom": "^21.0.3",
100
+ "markdown-it": "^15.0.2",
101
+ "prettier": "^3.9.8",
102
+ "tsup": "^8.5.1",
103
+ "typescript": "^6.0.3",
104
+ "typescript-eslint": "^8.70.0",
105
+ "vite": "^8.3.0",
106
+ "vitepress": "2.0.0-alpha.20",
107
+ "vitest": "^5.0.1",
108
+ "vue": "^3.5.0"
109
+ }
110
+ }