@mdream/nuxt 0.8.1 → 0.8.3

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 CHANGED
@@ -15,11 +15,11 @@ Mdream provides a Nuxt module that enables seamless HTML to Markdown conversion
15
15
  ### Installation
16
16
 
17
17
  ```bash
18
- npm install @mdream/nuxt mdream
18
+ npm install @mdream/nuxt
19
19
  # or
20
- pnpm add @mdream/nuxt mdream
20
+ pnpm add @mdream/nuxt
21
21
  # or
22
- yarn add @mdream/nuxt mdream
22
+ yarn add @mdream/nuxt
23
23
  ```
24
24
 
25
25
  ### Usage
package/dist/module.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mdream/nuxt",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "configKey": "mdream",
5
5
  "compatibility": {
6
6
  "nuxt": ">=3.0.0"
package/dist/module.mjs CHANGED
@@ -1,91 +1,68 @@
1
- import { join, dirname, relative, resolve } from 'node:path';
2
- import { useNuxt, defineNuxtModule, createResolver, addTypeTemplate, addServerHandler } from '@nuxt/kit';
1
+ import { join, relative, resolve } from 'node:path';
2
+ import { useNuxt, defineNuxtModule, createResolver, addTypeTemplate, addServerHandler, addPlugin } from '@nuxt/kit';
3
3
  import { defu } from 'defu';
4
4
  import { useSiteConfig, installNuxtSiteConfig } from 'nuxt-site-config/kit';
5
- import { mkdir, writeFile } from 'node:fs/promises';
5
+ import { writeFile } from 'node:fs/promises';
6
6
  import { consola } from 'consola';
7
- import { htmlToMarkdown, generateLlmsTxtArtifacts } from 'mdream';
7
+ import { generateLlmsTxtArtifacts } from 'mdream';
8
8
 
9
9
  const name = "@mdream/nuxt";
10
- const version = "0.8.0";
10
+ const version = "0.8.2";
11
11
 
12
12
  const logger = consola.withTag("nuxt-mdream");
13
- function isIndexable(html) {
14
- const robotsMatch = html.match(/<meta\s+name=["']robots["']\s+content=["']([^"']+)["']/i);
15
- if (robotsMatch) {
16
- const content = String(robotsMatch[1]).toLowerCase();
17
- return !content.includes("noindex");
18
- }
19
- return true;
20
- }
21
- function setupPrerenderHandler(config, nuxt = useNuxt()) {
13
+ function setupPrerenderHandler() {
14
+ const nuxt = useNuxt();
22
15
  const pages = [];
23
16
  nuxt.hooks.hook("nitro:init", async (nitro) => {
24
17
  nitro.hooks.hook("prerender:generate", async (route) => {
25
- if (!route.fileName?.endsWith(".html") || !route.contents) {
26
- return;
27
- }
28
- if (["/200.html", "/404.html"].includes(route.route)) {
29
- return;
30
- }
31
- const html = route.contents;
32
- if (!isIndexable(html)) {
33
- return;
34
- }
35
- try {
36
- const markdown = htmlToMarkdown(html, {
37
- origin: route.route,
38
- ...config.mdreamOptions
39
- });
40
- const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
41
- const title = titleMatch ? String(titleMatch[1]).trim() : route.route;
18
+ if (route.fileName?.endsWith(".md")) {
19
+ const markdown = route.contents;
20
+ const titleMatch = markdown.match(/title:\s*(.+)/);
21
+ const extractedTitle = titleMatch?.[1]?.replace(/"/g, "") || route.route;
42
22
  pages.push({
43
23
  url: route.route,
44
- title,
24
+ title: extractedTitle,
45
25
  markdown
46
26
  });
47
- const mdPath = route.route === "/" ? "/index.md" : `${route.route}.md`;
48
- const outputPath = join(nitro.options.output.publicDir, mdPath);
49
- await mkdir(dirname(outputPath), { recursive: true });
50
- await writeFile(outputPath, markdown, "utf-8");
51
- } catch (error) {
52
- logger.warn(`Failed to convert ${route.route} to markdown:`, error);
53
27
  }
54
28
  });
55
29
  nitro.hooks.hook("prerender:done", async () => {
56
30
  if (pages.length === 0) {
57
31
  return;
58
32
  }
59
- try {
60
- const processedFiles = pages.map((page) => ({
61
- title: page.title,
62
- content: page.markdown,
63
- url: page.url === "/" ? "/index.md" : `${page.url}.md`
64
- }));
65
- const siteConfig = useSiteConfig();
66
- const artifacts = await generateLlmsTxtArtifacts({
67
- files: processedFiles,
68
- generateFull: true,
69
- siteName: siteConfig.name || siteConfig.url,
70
- description: siteConfig.description
33
+ const processedFiles = pages.map((page) => ({
34
+ title: page.title,
35
+ content: page.markdown,
36
+ url: page.url === "/" ? "/index.md" : page.url.endsWith(".md") ? page.url : `${page.url}.md`
37
+ }));
38
+ const siteConfig = useSiteConfig();
39
+ const artifacts = await generateLlmsTxtArtifacts({
40
+ origin: siteConfig.url,
41
+ files: processedFiles,
42
+ generateFull: true,
43
+ siteName: siteConfig.name || siteConfig.url,
44
+ description: siteConfig.description
45
+ });
46
+ logger.success(`Generated markdown for ${pages.length} pages`);
47
+ if (artifacts.llmsTxt) {
48
+ const llmsTxtPath = join(nitro.options.output.publicDir, "llms.txt");
49
+ await writeFile(llmsTxtPath, artifacts.llmsTxt, "utf-8");
50
+ nitro._prerenderedRoutes.push({
51
+ route: "/llms.txt",
52
+ fileName: llmsTxtPath,
53
+ generateTimeMS: 0
71
54
  });
72
- if (artifacts.llmsTxt) {
73
- const llmsTxtPath = join(nitro.options.output.publicDir, "llms.txt");
74
- await writeFile(llmsTxtPath, artifacts.llmsTxt, "utf-8");
75
- }
76
- if (artifacts.llmsFullTxt) {
77
- const llmsFullTxtPath = join(nitro.options.output.publicDir, "llms-full.txt");
78
- await writeFile(llmsFullTxtPath, artifacts.llmsFullTxt, "utf-8");
79
- }
80
- logger.success(`Generated markdown for ${pages.length} pages`);
81
- if (artifacts.llmsTxt) {
82
- logger.info("Generated llms.txt");
83
- }
84
- if (artifacts.llmsFullTxt) {
85
- logger.info("Generated llms-full.txt");
86
- }
87
- } catch (error) {
88
- logger.error("Failed to generate llms.txt artifacts:", error);
55
+ logger.info("Generated llms.txt");
56
+ }
57
+ if (artifacts.llmsFullTxt) {
58
+ const llmsFullTxtPath = join(nitro.options.output.publicDir, "llms-full.txt");
59
+ await writeFile(llmsFullTxtPath, artifacts.llmsFullTxt, "utf-8");
60
+ nitro._prerenderedRoutes.push({
61
+ route: "/llms-full.txt",
62
+ fileName: llmsFullTxtPath,
63
+ generateTimeMS: 0
64
+ });
65
+ logger.info("Generated llms-full.txt");
89
66
  }
90
67
  });
91
68
  });
@@ -102,7 +79,9 @@ const module = defineNuxtModule({
102
79
  },
103
80
  defaults: {
104
81
  enabled: true,
105
- mdreamOptions: {},
82
+ mdreamOptions: {
83
+ preset: "minimal"
84
+ },
106
85
  cache: {
107
86
  maxAge: 3600,
108
87
  // 1 hour
@@ -168,9 +147,12 @@ export {}
168
147
  middleware: true,
169
148
  handler: resolver.resolve("./runtime/server/middleware/mdream")
170
149
  });
150
+ if (nuxt.options.build) {
151
+ addPlugin({ mode: "server", src: resolver.resolve("./runtime/nuxt/plugins/prerender") });
152
+ }
171
153
  const isStatic = nuxt.options.nitro.static || nuxt.options._generate || false;
172
154
  if (isStatic || nuxt.options.nitro.prerender?.routes?.length) {
173
- setupPrerenderHandler(runtimeConfig, nuxt);
155
+ setupPrerenderHandler();
174
156
  }
175
157
  }
176
158
  });
@@ -0,0 +1,2 @@
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
+ export default _default;
@@ -0,0 +1,20 @@
1
+ import { defineNuxtPlugin, prerenderRoutes } from "nuxt/app";
2
+ export default defineNuxtPlugin({
3
+ setup(nuxtApp) {
4
+ if (!import.meta.prerender) {
5
+ return;
6
+ }
7
+ nuxtApp.hooks.hook("app:rendered", (ctx) => {
8
+ let url = ctx.ssrContext?.url || "";
9
+ if (url.endsWith(".md")) {
10
+ return;
11
+ }
12
+ if (url.endsWith("/")) {
13
+ url = `${url}index.md`;
14
+ } else {
15
+ url = `${url}.md`;
16
+ }
17
+ prerenderRoutes(url);
18
+ });
19
+ }
20
+ });
@@ -1,2 +1,2 @@
1
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | undefined>>;
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | import("h3").H3Error<unknown> | undefined>>;
2
2
  export default _default;
@@ -1,18 +1,12 @@
1
+ import { withSiteUrl } from "#site-config/server/composables/utils";
1
2
  import { consola } from "consola";
2
- import { createError, defineEventHandler, getRequestURL, setHeader } from "h3";
3
+ import { createError, defineEventHandler, setHeader } from "h3";
3
4
  import { htmlToMarkdown } from "mdream";
5
+ import { extractionPlugin } from "mdream/plugins";
4
6
  import { withMinimalPreset } from "mdream/preset/minimal";
5
7
  import { useNitroApp, useRuntimeConfig } from "nitropack/runtime";
6
8
  const logger = consola.withTag("nuxt-mdream");
7
- function isIndexable(html) {
8
- const robotsMatch = html.match(/<meta\s+name=["']robots["']\s+content=["']([^"']+)["']/i);
9
- if (robotsMatch) {
10
- const content = String(robotsMatch[1]).toLowerCase();
11
- return !content.includes("noindex");
12
- }
13
- return true;
14
- }
15
- async function convertHtmlToMarkdown(html, url, config, route, title) {
9
+ async function convertHtmlToMarkdown(html, url, config, route) {
16
10
  let options = {
17
11
  origin: url,
18
12
  ...config.mdreamOptions
@@ -20,83 +14,60 @@ async function convertHtmlToMarkdown(html, url, config, route, title) {
20
14
  if (config.mdreamOptions?.preset === "minimal") {
21
15
  options = withMinimalPreset(options);
22
16
  }
23
- let markdown = htmlToMarkdown(html, options);
17
+ let title = "";
18
+ let markdown = htmlToMarkdown(html, {
19
+ ...options,
20
+ plugins: [
21
+ ...options.plugins || [],
22
+ // Add any additional plugins here if needed
23
+ extractionPlugin({
24
+ title(html2) {
25
+ title = html2.textContent;
26
+ }
27
+ })
28
+ ]
29
+ });
24
30
  const context = {
25
31
  html,
26
32
  markdown,
27
33
  route,
28
34
  title,
29
- isPrerender: false
35
+ isPrerender: Boolean(import.meta.prerender)
30
36
  };
31
- try {
32
- const nitroApp = useNitroApp();
33
- if (nitroApp?.hooks) {
34
- await nitroApp.hooks.callHook("mdream:markdown", context);
35
- markdown = context.markdown;
36
- }
37
- } catch (error) {
38
- logger.debug("Could not call mdream:markdown hook:", error);
37
+ const nitroApp = useNitroApp();
38
+ if (nitroApp?.hooks) {
39
+ await nitroApp.hooks.callHook("mdream:markdown", context);
40
+ markdown = context.markdown;
39
41
  }
40
42
  return markdown;
41
43
  }
42
44
  export default defineEventHandler(async (event) => {
43
- const requestUrl = getRequestURL(event);
44
- let htmlPath = requestUrl.pathname;
45
- if (!htmlPath.endsWith(".md")) {
45
+ let path = event.path;
46
+ if (!path.endsWith(".md")) {
46
47
  return;
47
48
  }
48
49
  const config = useRuntimeConfig(event).mdream;
49
- htmlPath = htmlPath.slice(0, -3);
50
- if (htmlPath === "/index") {
51
- htmlPath = "/";
50
+ path = path.slice(0, -3);
51
+ if (path === "/index") {
52
+ path = "/";
52
53
  }
54
+ let html;
53
55
  try {
54
- const fullUrl = new URL(htmlPath, requestUrl.origin).toString();
55
- const response = await fetch(fullUrl, {
56
- headers: {
57
- // Forward important headers
58
- "user-agent": event.headers.get("user-agent") || "",
59
- "accept-language": event.headers.get("accept-language") || ""
60
- }
61
- });
62
- if (!response.ok) {
63
- throw createError({
64
- statusCode: response.status,
65
- statusMessage: response.statusText
66
- });
67
- }
68
- const html = await response.text();
69
- if (!isIndexable(html)) {
70
- throw createError({
71
- statusCode: 404,
72
- statusMessage: "Page not indexable"
73
- });
74
- }
75
- const titleMatch = html.match(/<title[^>]*>([^<]+)<\/title>/i);
76
- const title = titleMatch ? String(titleMatch[1]).trim() : htmlPath;
77
- const markdown = await convertHtmlToMarkdown(
78
- html,
79
- requestUrl.origin + htmlPath,
80
- config,
81
- htmlPath,
82
- title
83
- );
84
- setHeader(event, "content-type", "text/markdown; charset=utf-8");
85
- return markdown;
86
- } catch (error) {
87
- if (error.statusCode) {
88
- throw error;
89
- }
90
- if (error.status === 404) {
91
- throw createError({
92
- statusCode: 404,
93
- statusMessage: "Page not found"
94
- });
95
- }
96
- logger.error("Failed to generate markdown for", htmlPath, error);
97
- throw createError({
56
+ html = await globalThis.$fetch(path);
57
+ } catch (e) {
58
+ logger.error(`Failed to fetch HTML for ${path}`, e);
59
+ return createError({
98
60
  statusCode: 500,
99
- statusMessage: `Failed to generate markdown: ${error.message || "Unknown error"}`
61
+ statusMessage: "Internal Server Error",
62
+ message: `Failed to fetch HTML for ${path}`
100
63
  });
101
64
  }
65
+ const markdown = await convertHtmlToMarkdown(
66
+ html,
67
+ withSiteUrl(event, path),
68
+ config,
69
+ path
70
+ );
71
+ setHeader(event, "content-type", "text/markdown; charset=utf-8");
72
+ return markdown;
102
73
  });
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mdream/nuxt",
3
3
  "type": "module",
4
- "version": "0.8.1",
4
+ "version": "0.8.3",
5
5
  "description": "Nuxt module for converting HTML pages to Markdown using mdream",
6
6
  "license": "MIT",
7
7
  "exports": {
@@ -22,29 +22,26 @@
22
22
  "files": [
23
23
  "dist"
24
24
  ],
25
- "peerDependencies": {
26
- "nuxt": "^3.0.0"
27
- },
28
25
  "dependencies": {
29
- "@nuxt/kit": "^4.0.0",
26
+ "@nuxt/kit": "^4.0.1",
30
27
  "consola": "^3.4.2",
31
28
  "defu": "^6.1.4",
32
29
  "nuxt-site-config": "^3.2.2",
33
30
  "pathe": "^2.0.3",
34
- "mdream": "0.8.1"
31
+ "mdream": "0.8.3"
35
32
  },
36
33
  "devDependencies": {
37
- "@antfu/eslint-config": "^4.17.0",
34
+ "@antfu/eslint-config": "^4.18.0",
38
35
  "@arethetypeswrong/cli": "^0.18.2",
39
36
  "@nuxt/devtools": "latest",
40
37
  "@nuxt/module-builder": "^1.0.1",
41
- "@nuxt/schema": "^4.0.0",
38
+ "@nuxt/schema": "^4.0.1",
42
39
  "@nuxt/test-utils": "^3.19.2",
43
40
  "@types/node": "latest",
44
41
  "bumpp": "^10.2.0",
45
42
  "changelogen": "^0.6.2",
46
43
  "eslint": "^9.31.0",
47
- "nuxt": "^4.0.0",
44
+ "nuxt": "^4.0.1",
48
45
  "typescript": "^5.8.3",
49
46
  "vitest": "^3.2.4"
50
47
  },