rehype-og-card 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 CHANGED
@@ -2,20 +2,20 @@
2
2
 
3
3
  Rehype plugin to convert text links to link cards inspired by [remark-link-card](https://github.com/gladevise/remark-link-card). Bare links are converted to link cards, but not links in lists or non-bare links.
4
4
 
5
- | Features | remark-link-card | rehype-og-card |
6
- | :--------------------------------------------- | :---------------------------------------------------------------------------------------: | :-----------------------: |
7
- | Plugin type | Remark | Rehype |
8
- | TypeScript support | ❌ | ✅ |
9
- | Convert bare links to link cards | ✅ | ✅ |
10
- | Shorten URLs displayed on link cards | ✅ | ✅ |
11
- | Server-side cache for images | ✅ | ✅ |
12
- | Cache images with long URLs | ❌ ([implemented](https://github.com/gladevise/remark-link-card/pull/16) but not released) | ✅ |
13
- | Build cache for Open Graph metadata and images | ❌ | Planned to be implemented |
14
- | Exclude links in lists from conversion | ❌ | ✅ |
15
- | Exclude specific domains from conversion | ❌ | ✅ |
16
- | Lazy loading images | ❌ | ✅ |
17
- | Async decoding images | ❌ | ✅ |
18
- | Customize the crawler's user agent string | ❌ | ✅ |
5
+ | Features | remark-link-card | rehype-og-card |
6
+ | :--------------------------------------------- | :---------------------------------------------------------------------------------------: | :------------: |
7
+ | Plugin type | Remark | Rehype |
8
+ | TypeScript support | ❌ | ✅ |
9
+ | Convert bare links to link cards | ✅ | ✅ |
10
+ | Shorten URLs displayed on link cards | ✅ | ✅ |
11
+ | Server-side cache for images | ✅ | ✅ |
12
+ | Cache images with long URLs | ❌ ([implemented](https://github.com/gladevise/remark-link-card/pull/16) but not released) | ✅ |
13
+ | Build cache for Open Graph metadata and images | ❌ | ✅ (image only) |
14
+ | Exclude links in lists from conversion | ❌ | ✅ |
15
+ | Exclude specific domains from conversion | ❌ | ✅ |
16
+ | Lazy loading images | ❌ | ✅ |
17
+ | Async decoding images | ❌ | ✅ |
18
+ | Customize the crawler's user agent string | ❌ | ✅ |
19
19
 
20
20
  ## Installation
21
21
 
@@ -101,6 +101,20 @@ The following links will NOT be converted to link cards:
101
101
 
102
102
  All options are optional.
103
103
 
104
+ ### `buildCache`
105
+
106
+ Whether to cache the OG metadata and images for the next build. `buildCache` option requires `serverCache` option to be enabled.
107
+
108
+ - Type: `boolean`
109
+ - Default: `false`
110
+
111
+ ### `buildCachePath`
112
+
113
+ Path to save the build cache. For Astro projects, specify the `./node_modules/.astro` directory.
114
+
115
+ - Type: `string`
116
+ - Default: `"./.cache/og-card"`
117
+
104
118
  ### `crawlerUserAgent`
105
119
 
106
120
  User agent to use for fetching the OG metadata and images.
@@ -145,7 +159,7 @@ Whether to cache the OG data on the server.
145
159
 
146
160
  ### `serverCachePath`
147
161
 
148
- Path to save the server cache.
162
+ Path to save the server cache. Specify the directory whose contents will be copied to the root. For example, for an Astro project, specify the `public` directory.
149
163
 
150
164
  - Type: `string`
151
165
  - Default: `"./public"`
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { RehypeOGCardOptions } from "./util.js";
2
1
  import type { Plugin } from "unified";
2
+ import { RehypeOGCardOptions } from "./types.js";
3
3
  import type { Root } from "hast";
4
4
  /**
5
5
  * Rehype plugin to create OG card from bare links.
package/dist/index.js CHANGED
@@ -7,11 +7,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { convertTextToAnchorElement, createOGCard, downloadImage, getOGData, isAnchorElement, isTextNode, isValidURL } from "./util.js";
10
+ import { convertTextToAnchorElement, downloadImage, isAnchorElement, isTextNode, isValidURL } from "./util.js";
11
+ import { createOGCard, getOGData } from "./openGraphUtil.js";
11
12
  import { isElement } from "hast-util-is-element";
12
13
  import path from "path";
13
14
  import { visitParents } from "unist-util-visit-parents";
14
15
  const DEFAULT_OPTIONS = {
16
+ buildCache: false,
17
+ buildCachePath: "./node_modules/.cache",
15
18
  crawlerUserAgent:
16
19
  // eslint-disable-next-line max-len
17
20
  "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36",
@@ -31,7 +34,11 @@ const DEFAULT_OPTIONS = {
31
34
  // eslint-disable-next-line max-lines-per-function
32
35
  const rehypeOGCard = (options) => {
33
36
  const mergedOptions = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
37
+ if (mergedOptions.buildCache && !mergedOptions.serverCache) {
38
+ throw new Error("[rehype-og-card] `buildCache` option requires `serverCache` option to be enabled.");
39
+ }
34
40
  mergedOptions.serverCachePath = path.posix.join(mergedOptions.serverCachePath, "./rehype-og-card/");
41
+ mergedOptions.buildCachePath = path.posix.join(mergedOptions.buildCachePath, "./rehype-og-card/");
35
42
  /**
36
43
  * Transform function to create OG card from bare links.
37
44
  * @param tree Root node of the HAST tree.
@@ -43,18 +50,18 @@ const rehypeOGCard = (options) => {
43
50
  visitParents(tree, ["element", "text"], (node, ancestors) => {
44
51
  var _a;
45
52
  let anchorNode = null;
46
- if (node.type === "text" && isValidURL(node.value.trim())) {
53
+ const isBareLink = node.type === "text" && isValidURL(node.value.trim());
54
+ const isValidAnchor = isAnchorElement(node) && isValidURL(node.properties.href);
55
+ if (isBareLink) {
47
56
  anchorNode = convertTextToAnchorElement(node);
48
57
  }
49
- else if (mergedOptions.enableSameTextURLConversion &&
50
- isAnchorElement(node) &&
51
- isValidURL(node.properties.href)) {
52
- const isBareLink =
58
+ else if (mergedOptions.enableSameTextURLConversion && isValidAnchor) {
59
+ const isSameTextURL =
53
60
  // eslint-disable-next-line no-magic-numbers
54
61
  node.children.length === 1 &&
55
62
  isTextNode(node.children[0]) &&
56
63
  node.children[0].value === node.properties.href;
57
- if (!isBareLink)
64
+ if (!isSameTextURL)
58
65
  return;
59
66
  anchorNode = node;
60
67
  }
@@ -80,14 +87,26 @@ const rehypeOGCard = (options) => {
80
87
  if (!OGData)
81
88
  return;
82
89
  if (OGData.OGImageURL && mergedOptions.serverCache) {
83
- const filename = yield downloadImage(OGData.OGImageURL, mergedOptions.serverCachePath, mergedOptions.crawlerUserAgent);
84
- if (filename) {
90
+ const filename = yield downloadImage({
91
+ buildCacheDirectory: mergedOptions.buildCachePath,
92
+ directly: mergedOptions.serverCachePath,
93
+ url: OGData.OGImageURL,
94
+ useBuildCache: mergedOptions.buildCache,
95
+ userAgent: mergedOptions.crawlerUserAgent
96
+ });
97
+ if (filename && mergedOptions.serverCache) {
85
98
  OGData.OGImageURL = path.posix.join("/rehype-og-card", filename);
86
99
  }
87
100
  }
88
101
  if (OGData.faviconURL && mergedOptions.serverCache) {
89
- const filename = yield downloadImage(OGData.faviconURL, mergedOptions.serverCachePath, mergedOptions.crawlerUserAgent);
90
- if (filename) {
102
+ const filename = yield downloadImage({
103
+ buildCacheDirectory: mergedOptions.buildCachePath,
104
+ directly: mergedOptions.serverCachePath,
105
+ url: OGData.faviconURL,
106
+ useBuildCache: mergedOptions.buildCache,
107
+ userAgent: mergedOptions.crawlerUserAgent
108
+ });
109
+ if (filename && mergedOptions.serverCache) {
91
110
  OGData.faviconURL = path.posix.join("/rehype-og-card", filename);
92
111
  }
93
112
  }
@@ -0,0 +1,31 @@
1
+ import type { Element } from "hast";
2
+ import { RehypeOGCardOptions } from "./types.js";
3
+ interface OGCardData {
4
+ url: string;
5
+ title: string;
6
+ description?: string;
7
+ displayURL: string;
8
+ faviconURL?: string;
9
+ OGImageURL?: string;
10
+ OGImageAlt?: string;
11
+ OGImageWidth?: number;
12
+ OGImageHeight?: number;
13
+ }
14
+ /**
15
+ * Get OG card data from given URL.
16
+ * @param url URL to get OG card data.
17
+ * @param userAgent User agent to use for fetching.
18
+ * @returns OG card data.
19
+ */
20
+ declare const getOGData: (url: string, userAgent: string) => Promise<OGCardData | null>;
21
+ /**
22
+ * Create OG card element from given data.
23
+ * @param data Data to create OG card.
24
+ * @param options Options to create OG card.
25
+ * @returns OG card element.
26
+ */
27
+ declare const createOGCard: (data: OGCardData, options: Required<{
28
+ loading: RehypeOGCardOptions["loading"];
29
+ decoding: RehypeOGCardOptions["decoding"];
30
+ }>) => Element;
31
+ export { OGCardData, createOGCard, getOGData };
@@ -0,0 +1,91 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import { h } from "hastscript";
11
+ import scraper from "open-graph-scraper";
12
+ /**
13
+ * Get OG card data from given URL.
14
+ * @param url URL to get OG card data.
15
+ * @param userAgent User agent to use for fetching.
16
+ * @returns OG card data.
17
+ */
18
+ const getOGData = (url, userAgent) => __awaiter(void 0, void 0, void 0, function* () {
19
+ try {
20
+ const { result } = yield scraper({
21
+ fetchOptions: {
22
+ headers: {
23
+ "user-agent": userAgent
24
+ }
25
+ },
26
+ url
27
+ });
28
+ const OGImage = result.ogImage ? result.ogImage[0] : null;
29
+ const faviconURL = result.favicon
30
+ ? `https://www.google.com/s2/favicons?domain=${new URL(url).hostname}`
31
+ : // eslint-disable-next-line no-undefined
32
+ undefined;
33
+ return {
34
+ OGImageAlt: OGImage === null || OGImage === void 0 ? void 0 : OGImage.alt,
35
+ OGImageHeight: OGImage === null || OGImage === void 0 ? void 0 : OGImage.height,
36
+ OGImageURL: OGImage === null || OGImage === void 0 ? void 0 : OGImage.url,
37
+ OGImageWidth: OGImage === null || OGImage === void 0 ? void 0 : OGImage.width,
38
+ description: result.ogDescription,
39
+ displayURL: url,
40
+ faviconURL,
41
+ title: result.ogTitle || url,
42
+ url
43
+ };
44
+ }
45
+ catch (error) {
46
+ // eslint-disable-next-line no-console
47
+ console.error("[rehype-og-card] Error fetching OG data:", error);
48
+ return null;
49
+ }
50
+ });
51
+ /**
52
+ * Create OG card element from given data.
53
+ * @param data Data to create OG card.
54
+ * @param options Options to create OG card.
55
+ * @returns OG card element.
56
+ */
57
+ const createOGCard = (data, options) => {
58
+ const card = h("div.og-card-container", [
59
+ h("a", { href: data.url }, [
60
+ h("div.og-card-info", [
61
+ h("div.og-card-title", data.title),
62
+ data.description ? h("div.og-card-description", data.description) : null,
63
+ h("div.og-card-url-container", [
64
+ h("img.og-card-favicon", {
65
+ alt: "favicon",
66
+ decoding: options.decoding,
67
+ height: 16,
68
+ loading: options.loading,
69
+ src: data.faviconURL,
70
+ width: 16
71
+ }),
72
+ h("span.og-card-url", data.displayURL)
73
+ ])
74
+ ]),
75
+ data.OGImageURL
76
+ ? h("div.og-card-image-container", [
77
+ h("img.og-card-image", {
78
+ alt: data.OGImageAlt || data.OGImageURL,
79
+ decoding: options.decoding,
80
+ height: data.OGImageHeight,
81
+ loading: options.loading,
82
+ src: data.OGImageURL,
83
+ width: data.OGImageWidth
84
+ })
85
+ ])
86
+ : null
87
+ ])
88
+ ]);
89
+ return card;
90
+ };
91
+ export { createOGCard, getOGData };
@@ -0,0 +1,53 @@
1
+ interface RehypeOGCardOptions {
2
+ /**
3
+ * Domains to exclude from conversion.
4
+ */
5
+ excludeDomains?: string[];
6
+ /**
7
+ * `decoding` attribute of the image element.
8
+ * @default "async"
9
+ */
10
+ decoding?: HTMLImageElement["decoding"];
11
+ /**
12
+ * `loading` attribute of the image element.
13
+ * @default "lazy"
14
+ */
15
+ loading?: HTMLImageElement["loading"];
16
+ /**
17
+ * Whether to shorten the displayed URL.
18
+ * @default true
19
+ */
20
+ shortenURL?: boolean;
21
+ /**
22
+ * Whether to enable converting the links
23
+ * that have the same URL and text (e.g., `[https://example.com](https://example.com)`).
24
+ * @default false
25
+ */
26
+ enableSameTextURLConversion?: boolean;
27
+ /**
28
+ * Whether to cache the OG data on the server.
29
+ * @default true
30
+ */
31
+ serverCache?: boolean;
32
+ /**
33
+ * Path to save the server cache.
34
+ * @default "./public"
35
+ */
36
+ serverCachePath?: string;
37
+ /**
38
+ * Whether to cache the OG metadata and images for the next build.
39
+ * @default false
40
+ */
41
+ buildCache?: boolean;
42
+ /**
43
+ * Path to save the build cache.
44
+ * @default "./node_modules/.cache"
45
+ */
46
+ buildCachePath?: string;
47
+ /**
48
+ * User agent to use for fetching the OG metadata and images.
49
+ * @default "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
50
+ */
51
+ crawlerUserAgent?: string;
52
+ }
53
+ export { RehypeOGCardOptions };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/util.d.ts CHANGED
@@ -1,46 +1,4 @@
1
1
  import type { Element, ElementContent, Text } from "hast";
2
- interface RehypeOGCardOptions {
3
- /**
4
- * Domains to exclude from conversion.
5
- */
6
- excludeDomains?: string[];
7
- /**
8
- * `decoding` attribute of the image element.
9
- * @default "async"
10
- */
11
- decoding?: HTMLImageElement["decoding"];
12
- /**
13
- * `loading` attribute of the image element.
14
- * @default "lazy"
15
- */
16
- loading?: HTMLImageElement["loading"];
17
- /**
18
- * Whether to shorten the displayed URL.
19
- * @default true
20
- */
21
- shortenURL?: boolean;
22
- /**
23
- * Whether to enable converting the links
24
- * that have the same URL and text (e.g., `[https://example.com](https://example.com)`).
25
- * @default false
26
- */
27
- enableSameTextURLConversion?: boolean;
28
- /**
29
- * Whether to cache the OG data on the server.
30
- * @default true
31
- */
32
- serverCache?: boolean;
33
- /**
34
- * Path to save the server cache.
35
- * @default "./public"
36
- */
37
- serverCachePath?: string;
38
- /**
39
- * User agent to use for fetching the OG metadata and images.
40
- * @default "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
41
- */
42
- crawlerUserAgent?: string;
43
- }
44
2
  interface AnchorElement extends Element {
45
3
  tagName: "a";
46
4
  properties: {
@@ -65,46 +23,38 @@ declare const isAnchorElement: (node: unknown) => node is AnchorElement;
65
23
  * @returns `true` if the URL is valid, `false` otherwise.
66
24
  */
67
25
  declare const isValidURL: (url: string) => boolean;
68
- interface OGCardData {
69
- url: string;
70
- title: string;
71
- description?: string;
72
- displayURL: string;
73
- faviconURL?: string;
74
- OGImageURL?: string;
75
- OGImageAlt?: string;
76
- OGImageWidth?: number;
77
- OGImageHeight?: number;
78
- }
79
- /**
80
- * Get OG card data from given URL.
81
- * @param url URL to get OG card data.
82
- * @param userAgent User agent to use for fetching.
83
- * @returns OG card data.
84
- */
85
- declare const getOGData: (url: string, userAgent: string) => Promise<OGCardData | null>;
86
- /**
87
- * Create OG card element from given data.
88
- * @param data Data to create OG card.
89
- * @param options Options to create OG card.
90
- * @returns OG card element.
91
- */
92
- declare const createOGCard: (data: OGCardData, options: Required<{
93
- loading: RehypeOGCardOptions["loading"];
94
- decoding: RehypeOGCardOptions["decoding"];
95
- }>) => Element;
96
26
  /**
97
27
  * Convert text node to anchor element. **This function does not check if the text is a valid URL.**
98
28
  * @param text Text node to convert.
99
29
  * @returns Anchor element.
100
30
  */
101
31
  declare const convertTextToAnchorElement: (text: Text) => AnchorElement;
32
+ interface DownloadImageOptions {
33
+ /**
34
+ * URL to download image.
35
+ */
36
+ url: string;
37
+ /**
38
+ * Directory to save the downloaded image.
39
+ */
40
+ directly: string;
41
+ /**
42
+ * Whether to save and restore the image cache.
43
+ */
44
+ useBuildCache: boolean;
45
+ /**
46
+ * Directory to save the image cache.
47
+ */
48
+ buildCacheDirectory: string;
49
+ /**
50
+ * User agent to use for fetching the image.
51
+ */
52
+ userAgent: string;
53
+ }
102
54
  /**
103
55
  * Download image from given URL.
104
- * @param url URL to download image.
105
- * @param directly Path to save the image.
106
- * @param userAgent User agent to use for fetching.
56
+ * @param options Options to download image.
107
57
  * @returns Path to the downloaded image.
108
58
  */
109
- declare const downloadImage: (url: string, directly: string, userAgent: string) => Promise<string | null>;
110
- export { AnchorElement, RehypeOGCardOptions, OGCardData, isTextNode, isAnchorElement, isValidURL, getOGData, createOGCard, convertTextToAnchorElement, downloadImage };
59
+ declare const downloadImage: (options: DownloadImageOptions) => Promise<string | null>;
60
+ export { AnchorElement, isTextNode, isAnchorElement, isValidURL, convertTextToAnchorElement, downloadImage };
package/dist/util.js CHANGED
@@ -9,10 +9,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { createHash } from "crypto";
11
11
  import fs from "fs/promises";
12
- import { h } from "hastscript";
13
12
  import { isElement } from "hast-util-is-element";
14
13
  import path from "path";
15
- import scraper from "open-graph-scraper";
16
14
  /**
17
15
  * Check if the node is a text node.
18
16
  * @param node Node to check.
@@ -31,93 +29,15 @@ const isAnchorElement = (node) => Boolean(node) && isElement(node, "a") && "href
31
29
  * @returns `true` if the URL is valid, `false` otherwise.
32
30
  */
33
31
  const isValidURL = (url) => {
32
+ const ALLOWED_PROTOCOLS = ["http:", "https:"];
34
33
  try {
35
- new URL(url);
36
- return true;
34
+ const { protocol } = new URL(url);
35
+ return ALLOWED_PROTOCOLS.includes(protocol.toLowerCase());
37
36
  }
38
37
  catch (_a) {
39
38
  return false;
40
39
  }
41
40
  };
42
- /**
43
- * Get OG card data from given URL.
44
- * @param url URL to get OG card data.
45
- * @param userAgent User agent to use for fetching.
46
- * @returns OG card data.
47
- */
48
- const getOGData = (url, userAgent) => __awaiter(void 0, void 0, void 0, function* () {
49
- try {
50
- const { result } = yield scraper({
51
- fetchOptions: {
52
- headers: {
53
- "user-agent": userAgent
54
- }
55
- },
56
- url
57
- });
58
- const OGImage = result.ogImage ? result.ogImage[0] : null;
59
- const faviconURL = result.favicon
60
- ? `https://www.google.com/s2/favicons?domain=${new URL(url).hostname}`
61
- : // eslint-disable-next-line no-undefined
62
- undefined;
63
- return {
64
- OGImageAlt: OGImage === null || OGImage === void 0 ? void 0 : OGImage.alt,
65
- OGImageHeight: OGImage === null || OGImage === void 0 ? void 0 : OGImage.height,
66
- OGImageURL: OGImage === null || OGImage === void 0 ? void 0 : OGImage.url,
67
- OGImageWidth: OGImage === null || OGImage === void 0 ? void 0 : OGImage.width,
68
- description: result.ogDescription,
69
- displayURL: url,
70
- faviconURL,
71
- title: result.ogTitle || url,
72
- url
73
- };
74
- }
75
- catch (error) {
76
- // eslint-disable-next-line no-console
77
- console.error("[rehype-og-card] Error fetching OG data:", error);
78
- return null;
79
- }
80
- });
81
- /**
82
- * Create OG card element from given data.
83
- * @param data Data to create OG card.
84
- * @param options Options to create OG card.
85
- * @returns OG card element.
86
- */
87
- const createOGCard = (data, options) => {
88
- const card = h("div.og-card-container", [
89
- h("a", { href: data.url }, [
90
- h("div.og-card-info", [
91
- h("div.og-card-title", data.title),
92
- data.description ? h("div.og-card-description", data.description) : null,
93
- h("div.og-card-url-container", [
94
- h("img.og-card-favicon", {
95
- alt: "favicon",
96
- decoding: options.decoding,
97
- height: 16,
98
- loading: options.loading,
99
- src: data.faviconURL,
100
- width: 16
101
- }),
102
- h("span.og-card-url", data.displayURL)
103
- ])
104
- ]),
105
- data.OGImageURL
106
- ? h("div.og-card-image-container", [
107
- h("img.og-card-image", {
108
- alt: data.OGImageAlt || data.OGImageURL,
109
- decoding: options.decoding,
110
- height: data.OGImageHeight,
111
- loading: options.loading,
112
- src: data.OGImageURL,
113
- width: data.OGImageWidth
114
- })
115
- ])
116
- : null
117
- ])
118
- ]);
119
- return card;
120
- };
121
41
  /**
122
42
  * Convert text node to anchor element. **This function does not check if the text is a valid URL.**
123
43
  * @param text Text node to convert.
@@ -131,33 +51,61 @@ const convertTextToAnchorElement = (text) => ({
131
51
  tagName: "a",
132
52
  type: "element"
133
53
  });
54
+ /**
55
+ * Check if the file exists.
56
+ * @param filePath Path to check.
57
+ * @returns `true` if the file exists, `false` otherwise.
58
+ */
59
+ const checkFileExists = (filePath) => __awaiter(void 0, void 0, void 0, function* () {
60
+ try {
61
+ yield fs.access(filePath);
62
+ return true;
63
+ }
64
+ catch (_a) {
65
+ return false;
66
+ }
67
+ });
134
68
  /**
135
69
  * Download image from given URL.
136
- * @param url URL to download image.
137
- * @param directly Path to save the image.
138
- * @param userAgent User agent to use for fetching.
70
+ * @param options Options to download image.
139
71
  * @returns Path to the downloaded image.
140
72
  */
141
73
  // eslint-disable-next-line max-statements
142
- const downloadImage = (url, directly, userAgent) => __awaiter(void 0, void 0, void 0, function* () {
143
- if (!isValidURL(url)) {
74
+ const downloadImage = (options) => __awaiter(void 0, void 0, void 0, function* () {
75
+ if (!isValidURL(options.url)) {
144
76
  // eslint-disable-next-line no-console
145
- console.error("[rehype-og-card] Invalid thumbnail URL:", url);
77
+ console.error("[rehype-og-card] Invalid thumbnail URL:", options.url);
146
78
  return null;
147
79
  }
148
80
  try {
149
- const response = yield fetch(url, {
81
+ const hash = createHash("sha256").update(options.url).digest("hex");
82
+ const filename = hash + path.extname(new URL(options.url).pathname);
83
+ const savePath = path.posix.join(options.directly, filename);
84
+ const buildCachePath = path.posix.join(options.buildCacheDirectory, filename);
85
+ // If the file already exists, return the filename.
86
+ const fileExists = yield checkFileExists(savePath);
87
+ if (fileExists) {
88
+ return filename;
89
+ }
90
+ const buildCacheExists = yield checkFileExists(buildCachePath);
91
+ if (options.useBuildCache && buildCacheExists) {
92
+ yield fs.mkdir(options.directly, { recursive: true });
93
+ yield fs.copyFile(buildCachePath, savePath);
94
+ return filename;
95
+ }
96
+ const response = yield fetch(options.url, {
150
97
  headers: {
151
- "user-agent": userAgent
98
+ "user-agent": options.userAgent
152
99
  }
153
100
  });
154
101
  const arrayBuffer = yield response.arrayBuffer();
155
102
  const buffer = Buffer.from(arrayBuffer);
156
- const hash = createHash("sha256").update(url).digest("hex");
157
- const filename = hash + path.extname(new URL(url).pathname);
158
- const savePath = path.posix.join(directly, filename);
159
- yield fs.mkdir(directly, { recursive: true });
103
+ yield fs.mkdir(options.directly, { recursive: true });
160
104
  yield fs.writeFile(savePath, buffer);
105
+ if (options.useBuildCache) {
106
+ yield fs.mkdir(options.buildCacheDirectory, { recursive: true });
107
+ yield fs.writeFile(buildCachePath, buffer);
108
+ }
161
109
  return filename;
162
110
  }
163
111
  catch (error) {
@@ -166,4 +114,4 @@ const downloadImage = (url, directly, userAgent) => __awaiter(void 0, void 0, vo
166
114
  return null;
167
115
  }
168
116
  });
169
- export { isTextNode, isAnchorElement, isValidURL, getOGData, createOGCard, convertTextToAnchorElement, downloadImage };
117
+ export { isTextNode, isAnchorElement, isValidURL, convertTextToAnchorElement, downloadImage };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rehype-og-card",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Rehype plugin to convert text links to link cards",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",