rehype-og-card 0.3.2 → 0.4.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
@@ -10,7 +10,7 @@ Rehype plugin to convert text links to link cards inspired by [remark-link-card]
10
10
  | Shorten URLs displayed on link cards | ✅ | ✅ |
11
11
  | Server-side cache for images | ✅ | ✅ |
12
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) |
13
+ | Build cache for Open Graph metadata and images | ❌ | ✅ |
14
14
  | Exclude links in lists from conversion | ❌ | ✅ |
15
15
  | Exclude specific domains from conversion | ❌ | ✅ |
16
16
  | Lazy loading images | ❌ | ✅ |
package/dist/index.js CHANGED
@@ -8,8 +8,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  import { convertTextToAnchorElement, createOGCard, isAnchorElement, isTextNode } from "./util/hast.js";
11
+ import { checkFileExistsSync, createDirectorySync } from "./util/file.js";
11
12
  import { downloadImage, getOGData, isValidURL } from "./util/network.js";
12
- import { restoreBuildCache, saveBuildCache } from "./util/cache.js";
13
+ import { restoreBuildCache, restoreOGDataBuildCache, saveBuildCacheFile, saveOGDataBuildCache } from "./util/cache.js";
13
14
  import { isElement } from "hast-util-is-element";
14
15
  import path from "path";
15
16
  import { visitParents } from "unist-util-visit-parents";
@@ -32,7 +33,7 @@ const DEFAULT_OPTIONS = {
32
33
  * @param options Plugin options.
33
34
  * @returns Transformer function.
34
35
  */
35
- // eslint-disable-next-line max-lines-per-function
36
+ // eslint-disable-next-line max-lines-per-function, max-statements
36
37
  const rehypeOGCard = (options) => {
37
38
  const mergedOptions = Object.assign(Object.assign({}, DEFAULT_OPTIONS), options);
38
39
  if (mergedOptions.buildCache && !mergedOptions.serverCache) {
@@ -40,15 +41,21 @@ const rehypeOGCard = (options) => {
40
41
  }
41
42
  mergedOptions.serverCachePath = path.posix.join(mergedOptions.serverCachePath, "./rehype-og-card/");
42
43
  mergedOptions.buildCachePath = path.posix.join(mergedOptions.buildCachePath, "./rehype-og-card/");
44
+ const buildCacheExists = checkFileExistsSync(mergedOptions.buildCachePath);
45
+ if (mergedOptions.buildCache) {
46
+ if (buildCacheExists) {
47
+ restoreBuildCache(mergedOptions.buildCachePath, mergedOptions.serverCachePath);
48
+ }
49
+ else {
50
+ createDirectorySync(mergedOptions.buildCachePath);
51
+ }
52
+ }
43
53
  /**
44
54
  * Transform function to create OG card from bare links.
45
55
  * @param tree Root node of the HAST tree.
46
56
  */
47
57
  // eslint-disable-next-line max-lines-per-function
48
58
  const transform = (tree) => __awaiter(void 0, void 0, void 0, function* () {
49
- if (mergedOptions.buildCache) {
50
- yield restoreBuildCache(mergedOptions.buildCachePath, mergedOptions.serverCachePath);
51
- }
52
59
  const linkCardPromises = [];
53
60
  // eslint-disable-next-line max-statements, max-lines-per-function
54
61
  visitParents(tree, ["element", "text"], (node, ancestors) => {
@@ -85,30 +92,48 @@ const rehypeOGCard = (options) => {
85
92
  const targetURL = new URL(anchorNode.properties.href);
86
93
  if ((_a = mergedOptions.excludeDomains) === null || _a === void 0 ? void 0 : _a.includes(targetURL.hostname))
87
94
  return;
88
- // eslint-disable-next-line jsdoc/require-jsdoc, max-statements
95
+ // eslint-disable-next-line jsdoc/require-jsdoc, max-statements, max-lines-per-function
89
96
  const linkCardPromise = () => __awaiter(void 0, void 0, void 0, function* () {
90
- const OGData = yield getOGData(anchorNode.properties.href, mergedOptions.crawlerUserAgent);
91
- if (!OGData)
92
- return;
97
+ let OGData = mergedOptions.buildCache
98
+ ? yield restoreOGDataBuildCache(anchorNode.properties.href, mergedOptions.buildCachePath)
99
+ : null;
100
+ if (!OGData) {
101
+ OGData = yield getOGData(anchorNode.properties.href, mergedOptions.crawlerUserAgent);
102
+ if (!OGData)
103
+ return;
104
+ if (mergedOptions.buildCache) {
105
+ yield saveOGDataBuildCache(anchorNode.properties.href, OGData, mergedOptions.buildCachePath);
106
+ }
107
+ }
93
108
  if (OGData.OGImageURL && mergedOptions.serverCache) {
94
109
  const filename = yield downloadImage({
95
- directly: mergedOptions.serverCachePath,
110
+ directory: mergedOptions.serverCachePath,
96
111
  url: OGData.OGImageURL,
97
112
  userAgent: mergedOptions.crawlerUserAgent
98
113
  });
99
114
  if (filename && mergedOptions.serverCache) {
100
115
  OGData.OGImageURL = path.posix.join("/rehype-og-card", filename);
101
116
  }
117
+ if (filename && mergedOptions.buildCache) {
118
+ const downloadedFilePath = path.join(mergedOptions.serverCachePath, filename);
119
+ const buildCachePath = path.join(mergedOptions.buildCachePath, filename);
120
+ yield saveBuildCacheFile(downloadedFilePath, buildCachePath);
121
+ }
102
122
  }
103
123
  if (OGData.faviconURL && mergedOptions.serverCache) {
104
124
  const filename = yield downloadImage({
105
- directly: mergedOptions.serverCachePath,
125
+ directory: mergedOptions.serverCachePath,
106
126
  url: OGData.faviconURL,
107
127
  userAgent: mergedOptions.crawlerUserAgent
108
128
  });
109
129
  if (filename && mergedOptions.serverCache) {
110
130
  OGData.faviconURL = path.posix.join("/rehype-og-card", filename);
111
131
  }
132
+ if (filename && mergedOptions.buildCache) {
133
+ const downloadedFilePath = path.join(mergedOptions.serverCachePath, filename);
134
+ const buildCachePath = path.join(mergedOptions.buildCachePath, filename);
135
+ yield saveBuildCacheFile(downloadedFilePath, buildCachePath);
136
+ }
112
137
  }
113
138
  if (mergedOptions.shortenURL) {
114
139
  const shortenedURL = new URL(anchorNode.properties.href);
@@ -126,9 +151,6 @@ const rehypeOGCard = (options) => {
126
151
  linkCardPromises.push(linkCardPromise());
127
152
  });
128
153
  yield Promise.all(linkCardPromises);
129
- if (mergedOptions.buildCache) {
130
- yield saveBuildCache(mergedOptions.serverCachePath, mergedOptions.buildCachePath);
131
- }
132
154
  });
133
155
  return transform;
134
156
  };
@@ -1,13 +1,28 @@
1
+ import { OGCardData } from "../types.js";
1
2
  /**
2
3
  * Save build cache if server cache exists.
3
4
  * @param serverCachePath Server cache path.
4
5
  * @param buildCachePath Build cache path.
5
6
  */
6
- declare const saveBuildCache: (serverCachePath: string, buildCachePath: string) => Promise<void>;
7
+ declare const saveBuildCacheFile: (serverCachePath: string, buildCachePath: string) => Promise<void>;
7
8
  /**
8
9
  * Restore build cache if it exists.
9
10
  * @param buildCachePath Build cache path.
10
11
  * @param serverCachePath Server cache path.
11
12
  */
12
- declare const restoreBuildCache: (buildCachePath: string, serverCachePath: string) => Promise<void>;
13
- export { saveBuildCache, restoreBuildCache };
13
+ declare const restoreBuildCache: (buildCachePath: string, serverCachePath: string) => void;
14
+ /**
15
+ * Save OG data to build cache.
16
+ * @param url URL of the OG data.
17
+ * @param OGData OG data to save.
18
+ * @param buildCachePath Build cache path.
19
+ */
20
+ declare const saveOGDataBuildCache: (url: string, OGData: OGCardData, buildCachePath: string) => Promise<void>;
21
+ /**
22
+ * Restore OG data from build cache.
23
+ * @param url URL of the OG data.
24
+ * @param buildCachePath Build cache path.
25
+ * @returns Restored OG data. If not found, returns `null`.
26
+ */
27
+ declare const restoreOGDataBuildCache: (url: string, buildCachePath: string) => Promise<OGCardData | null>;
28
+ export { saveBuildCacheFile, restoreBuildCache, saveOGDataBuildCache, restoreOGDataBuildCache };
@@ -7,46 +7,59 @@ 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 { checkFileExists } from "./file.js";
10
+ import { checkFileExists, copyDirectory, generateFilename } from "./file.js";
11
11
  import fs from "fs/promises";
12
12
  import path from "path";
13
13
  /**
14
- * Copy directory from source to destination.
15
- * @param source Source directory.
16
- * @param destination Destination directory.
14
+ * Save build cache if server cache exists.
15
+ * @param serverCachePath Server cache path.
16
+ * @param buildCachePath Build cache path.
17
17
  */
18
- // eslint-disable-next-line max-statements
19
- const copyDirectory = (source, destination) => __awaiter(void 0, void 0, void 0, function* () {
20
- const sourceExists = yield checkFileExists(source);
21
- if (!sourceExists)
22
- return;
23
- const destinationExists = yield checkFileExists(destination);
24
- if (!destinationExists) {
25
- yield fs.mkdir(destination, { recursive: true });
26
- }
27
- const files = yield fs.readdir(source);
28
- const promises = [];
29
- for (const file of files) {
30
- const sourceFile = path.join(source, file);
31
- const destFile = path.join(destination, file);
32
- promises.push(fs.copyFile(sourceFile, destFile));
18
+ const saveBuildCacheFile = (serverCachePath, buildCachePath) => __awaiter(void 0, void 0, void 0, function* () {
19
+ const serverCacheExists = yield checkFileExists(serverCachePath);
20
+ const buildCacheExists = yield checkFileExists(buildCachePath);
21
+ if (serverCacheExists && !buildCacheExists) {
22
+ yield fs.copyFile(serverCachePath, buildCachePath);
33
23
  }
34
- yield Promise.all(promises);
35
24
  });
36
25
  /**
37
- * Save build cache if server cache exists.
26
+ * Restore build cache if it exists.
27
+ * @param buildCachePath Build cache path.
38
28
  * @param serverCachePath Server cache path.
29
+ */
30
+ const restoreBuildCache = (buildCachePath, serverCachePath) => {
31
+ copyDirectory(buildCachePath, serverCachePath);
32
+ };
33
+ /**
34
+ * Save OG data to build cache.
35
+ * @param url URL of the OG data.
36
+ * @param OGData OG data to save.
39
37
  * @param buildCachePath Build cache path.
40
38
  */
41
- const saveBuildCache = (serverCachePath, buildCachePath) => __awaiter(void 0, void 0, void 0, function* () {
42
- yield copyDirectory(serverCachePath, buildCachePath);
39
+ const saveOGDataBuildCache = (url, OGData, buildCachePath) => __awaiter(void 0, void 0, void 0, function* () {
40
+ const filename = generateFilename(url, false);
41
+ const savePath = `${path.join(buildCachePath, filename)}.json`;
42
+ const cacheDirectoryExists = yield checkFileExists(buildCachePath);
43
+ if (!cacheDirectoryExists) {
44
+ yield fs.mkdir(buildCachePath, { recursive: true });
45
+ }
46
+ yield fs.writeFile(savePath, JSON.stringify(OGData));
43
47
  });
44
48
  /**
45
- * Restore build cache if it exists.
49
+ * Restore OG data from build cache.
50
+ * @param url URL of the OG data.
46
51
  * @param buildCachePath Build cache path.
47
- * @param serverCachePath Server cache path.
52
+ * @returns Restored OG data. If not found, returns `null`.
48
53
  */
49
- const restoreBuildCache = (buildCachePath, serverCachePath) => __awaiter(void 0, void 0, void 0, function* () {
50
- yield copyDirectory(buildCachePath, serverCachePath);
54
+ const restoreOGDataBuildCache = (url, buildCachePath) => __awaiter(void 0, void 0, void 0, function* () {
55
+ const filename = generateFilename(url, false);
56
+ const savePath = `${path.join(buildCachePath, filename)}.json`;
57
+ try {
58
+ const data = yield fs.readFile(savePath, "utf-8");
59
+ return JSON.parse(data);
60
+ }
61
+ catch (_a) {
62
+ return null;
63
+ }
51
64
  });
52
- export { saveBuildCache, restoreBuildCache };
65
+ export { saveBuildCacheFile, restoreBuildCache, saveOGDataBuildCache, restoreOGDataBuildCache };
@@ -4,4 +4,28 @@
4
4
  * @returns `true` if the file exists, `false` otherwise.
5
5
  */
6
6
  declare const checkFileExists: (filePath: string) => Promise<boolean>;
7
- export { checkFileExists };
7
+ /**
8
+ * Check if the file exists.
9
+ * @param filePath Path to check.
10
+ * @returns `true` if the file exists, `false` otherwise.
11
+ */
12
+ declare const checkFileExistsSync: (filePath: string) => boolean;
13
+ /**
14
+ * Create directory.
15
+ * @param dir Directory path.
16
+ */
17
+ declare const createDirectorySync: (dir: string) => void;
18
+ /**
19
+ * Copy directory from source to destination.
20
+ * @param source Source directory.
21
+ * @param destination Destination directory.
22
+ */
23
+ declare const copyDirectory: (source: string, destination: string) => void;
24
+ /**
25
+ * Generate filename from URL.
26
+ * @param url URL to generate filename.
27
+ * @param extension Include extension in the filename.
28
+ * @returns Generated filename.
29
+ */
30
+ declare const generateFilename: (url: string, extension?: boolean) => string;
31
+ export { checkFileExists, checkFileExistsSync, createDirectorySync, copyDirectory, generateFilename };
package/dist/util/file.js CHANGED
@@ -7,7 +7,9 @@ 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 fs from "fs/promises";
10
+ import { createHash } from "crypto";
11
+ import fs from "fs";
12
+ import path from "path";
11
13
  /**
12
14
  * Check if the file exists.
13
15
  * @param filePath Path to check.
@@ -15,11 +17,66 @@ import fs from "fs/promises";
15
17
  */
16
18
  const checkFileExists = (filePath) => __awaiter(void 0, void 0, void 0, function* () {
17
19
  try {
18
- yield fs.access(filePath);
20
+ yield fs.promises.access(filePath);
19
21
  return true;
20
22
  }
21
23
  catch (_a) {
22
24
  return false;
23
25
  }
24
26
  });
25
- export { checkFileExists };
27
+ /**
28
+ * Check if the file exists.
29
+ * @param filePath Path to check.
30
+ * @returns `true` if the file exists, `false` otherwise.
31
+ */
32
+ const checkFileExistsSync = (filePath) => {
33
+ let result = false;
34
+ fs.access(filePath, (error) => {
35
+ result = !error;
36
+ });
37
+ return result;
38
+ };
39
+ /**
40
+ * Create directory.
41
+ * @param dir Directory path.
42
+ */
43
+ const createDirectorySync = (dir) => {
44
+ fs.mkdir(dir, { recursive: true }, (error) => {
45
+ if (error) {
46
+ // eslint-disable-next-line no-console
47
+ console.error("[rehype-og-card] Failed to copy files: ", error);
48
+ }
49
+ });
50
+ };
51
+ /**
52
+ * Copy directory from source to destination.
53
+ * @param source Source directory.
54
+ * @param destination Destination directory.
55
+ */
56
+ const copyDirectory = (source, destination) => {
57
+ const sourceExists = checkFileExistsSync(source);
58
+ if (!sourceExists)
59
+ return;
60
+ const destinationExists = checkFileExistsSync(destination);
61
+ if (!destinationExists) {
62
+ createDirectorySync(destination);
63
+ }
64
+ fs.cp(source, destination, { recursive: true }, (error) => {
65
+ if (error) {
66
+ // eslint-disable-next-line no-console
67
+ console.error("[rehype-og-card] Failed to copy files: ", error);
68
+ }
69
+ });
70
+ };
71
+ /**
72
+ * Generate filename from URL.
73
+ * @param url URL to generate filename.
74
+ * @param extension Include extension in the filename.
75
+ * @returns Generated filename.
76
+ */
77
+ const generateFilename = (url, extension = true) => {
78
+ const hash = createHash("sha256").update(url).digest("hex");
79
+ const filename = extension ? hash + path.extname(new URL(url).pathname) : hash;
80
+ return filename;
81
+ };
82
+ export { checkFileExists, checkFileExistsSync, createDirectorySync, copyDirectory, generateFilename };
@@ -20,16 +20,16 @@ interface DownloadImageOptions {
20
20
  /**
21
21
  * Directory to save the downloaded image.
22
22
  */
23
- directly: string;
23
+ directory: string;
24
24
  /**
25
25
  * User agent to use for fetching the image.
26
26
  */
27
27
  userAgent: string;
28
28
  }
29
29
  /**
30
- * Download image from given URL.
30
+ * Download image from given URL. If the image already exists, return the filename.
31
31
  * @param options Options to download image.
32
- * @returns Path to the downloaded image.
32
+ * @returns Filename of the downloaded image.
33
33
  */
34
34
  declare const downloadImage: (options: DownloadImageOptions) => Promise<string | null>;
35
35
  export { isValidURL, getOGData, downloadImage };
@@ -7,8 +7,7 @@ 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 { checkFileExists } from "./file.js";
11
- import { createHash } from "crypto";
10
+ import { checkFileExists, generateFilename } from "./file.js";
12
11
  import fs from "fs/promises";
13
12
  import path from "path";
14
13
  import scraper from "open-graph-scraper";
@@ -67,9 +66,9 @@ const getOGData = (url, userAgent) => __awaiter(void 0, void 0, void 0, function
67
66
  }
68
67
  });
69
68
  /**
70
- * Download image from given URL.
69
+ * Download image from given URL. If the image already exists, return the filename.
71
70
  * @param options Options to download image.
72
- * @returns Path to the downloaded image.
71
+ * @returns Filename of the downloaded image.
73
72
  */
74
73
  // eslint-disable-next-line max-statements
75
74
  const downloadImage = (options) => __awaiter(void 0, void 0, void 0, function* () {
@@ -79,14 +78,12 @@ const downloadImage = (options) => __awaiter(void 0, void 0, void 0, function* (
79
78
  return null;
80
79
  }
81
80
  try {
82
- const hash = createHash("sha256").update(options.url).digest("hex");
83
- const filename = hash + path.extname(new URL(options.url).pathname);
84
- const savePath = path.posix.join(options.directly, filename);
81
+ const filename = generateFilename(options.url);
82
+ const savePath = path.posix.join(options.directory, filename);
85
83
  // If the file already exists, return the filename.
86
84
  const fileExists = yield checkFileExists(savePath);
87
- if (fileExists) {
85
+ if (fileExists)
88
86
  return filename;
89
- }
90
87
  const response = yield fetch(options.url, {
91
88
  headers: {
92
89
  "user-agent": options.userAgent
@@ -94,7 +91,10 @@ const downloadImage = (options) => __awaiter(void 0, void 0, void 0, function* (
94
91
  });
95
92
  const arrayBuffer = yield response.arrayBuffer();
96
93
  const buffer = Buffer.from(arrayBuffer);
97
- yield fs.mkdir(options.directly, { recursive: true });
94
+ const saveDirectoryExists = yield checkFileExists(options.directory);
95
+ if (!saveDirectoryExists) {
96
+ yield fs.mkdir(options.directory, { recursive: true });
97
+ }
98
98
  yield fs.writeFile(savePath, buffer);
99
99
  return filename;
100
100
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rehype-og-card",
3
- "version": "0.3.2",
3
+ "version": "0.4.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",