rehype-og-card 1.0.1 → 1.0.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/dist/index.d.ts +2 -2
- package/dist/index.js +16 -23
- package/dist/types.d.ts +1 -1
- package/dist/util/cache.d.ts +1 -1
- package/dist/util/cache.js +14 -23
- package/dist/util/file.js +4 -13
- package/dist/util/hast.d.ts +2 -2
- package/dist/util/hast.js +2 -1
- package/dist/util/network.js +17 -25
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Plugin } from "unified";
|
|
2
|
-
import { RehypeOGCardOptions } from "./types.js";
|
|
2
|
+
import type { RehypeOGCardOptions } from "./types.js";
|
|
3
3
|
import type { Root } from "hast";
|
|
4
4
|
/**
|
|
5
5
|
* Rehype plugin to create OG card from bare links.
|
|
@@ -8,4 +8,4 @@ import type { Root } from "hast";
|
|
|
8
8
|
*/
|
|
9
9
|
declare const rehypeOGCard: Plugin<[RehypeOGCardOptions | undefined], Root>;
|
|
10
10
|
export default rehypeOGCard;
|
|
11
|
-
export { RehypeOGCardOptions };
|
|
11
|
+
export type { RehypeOGCardOptions };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
import { convertTextToAnchorElement, createOGCard, isAnchorElement, isTextNode } from "./util/hast.js";
|
|
11
2
|
import { checkFileExistsSync, createDirectorySync } from "./util/file.js";
|
|
12
3
|
import { downloadImage, getOGData, isValidURL } from "./util/network.js";
|
|
@@ -36,7 +27,10 @@ const DEFAULT_OPTIONS = {
|
|
|
36
27
|
*/
|
|
37
28
|
// eslint-disable-next-line max-lines-per-function, max-statements
|
|
38
29
|
const rehypeOGCard = (options) => {
|
|
39
|
-
const mergedOptions =
|
|
30
|
+
const mergedOptions = {
|
|
31
|
+
...DEFAULT_OPTIONS,
|
|
32
|
+
...options
|
|
33
|
+
};
|
|
40
34
|
if (mergedOptions.buildCache && !mergedOptions.serverCache) {
|
|
41
35
|
throw new Error("[rehype-og-card] `buildCache` option requires `serverCache` option to be enabled.");
|
|
42
36
|
}
|
|
@@ -56,11 +50,10 @@ const rehypeOGCard = (options) => {
|
|
|
56
50
|
* @param tree Root node of the HAST tree.
|
|
57
51
|
*/
|
|
58
52
|
// eslint-disable-next-line max-lines-per-function
|
|
59
|
-
const transform = (tree) =>
|
|
53
|
+
const transform = async (tree) => {
|
|
60
54
|
const linkCardPromises = [];
|
|
61
55
|
// eslint-disable-next-line max-statements, max-lines-per-function
|
|
62
56
|
visitParents(tree, ["element", "text"], (node, ancestors) => {
|
|
63
|
-
var _a;
|
|
64
57
|
let anchorNode = null;
|
|
65
58
|
const isBareLink = node.type === "text" && isValidURL(node.value.trim());
|
|
66
59
|
const isValidAnchor = isAnchorElement(node) && isValidURL(node.properties.href);
|
|
@@ -91,15 +84,15 @@ const rehypeOGCard = (options) => {
|
|
|
91
84
|
if (!isTheOnlyChild)
|
|
92
85
|
return;
|
|
93
86
|
const targetURL = new URL(anchorNode.properties.href);
|
|
94
|
-
if (
|
|
87
|
+
if (mergedOptions.excludeDomains.includes(targetURL.hostname))
|
|
95
88
|
return;
|
|
96
89
|
// eslint-disable-next-line jsdoc/require-jsdoc, max-statements, max-lines-per-function
|
|
97
|
-
const linkCardPromise = () =>
|
|
90
|
+
const linkCardPromise = async () => {
|
|
98
91
|
let OGData = mergedOptions.buildCache
|
|
99
|
-
?
|
|
92
|
+
? await restoreOGDataBuildCache(anchorNode.properties.href, mergedOptions.buildCachePath)
|
|
100
93
|
: null;
|
|
101
94
|
if (!OGData) {
|
|
102
|
-
OGData =
|
|
95
|
+
OGData = await getOGData(anchorNode.properties.href, mergedOptions.crawlerUserAgent);
|
|
103
96
|
if (!OGData)
|
|
104
97
|
return;
|
|
105
98
|
if (mergedOptions.buildCache) {
|
|
@@ -107,12 +100,12 @@ const rehypeOGCard = (options) => {
|
|
|
107
100
|
}
|
|
108
101
|
}
|
|
109
102
|
if (OGData.OGImageURL && mergedOptions.serverCache) {
|
|
110
|
-
const filename =
|
|
103
|
+
const filename = await downloadImage({
|
|
111
104
|
directory: mergedOptions.serverCachePath,
|
|
112
105
|
url: OGData.OGImageURL,
|
|
113
106
|
userAgent: mergedOptions.crawlerUserAgent
|
|
114
107
|
});
|
|
115
|
-
if (filename
|
|
108
|
+
if (filename) {
|
|
116
109
|
OGData.OGImageURL = path.posix.join("/rehype-og-card", filename);
|
|
117
110
|
}
|
|
118
111
|
if (filename && mergedOptions.buildCache) {
|
|
@@ -122,12 +115,12 @@ const rehypeOGCard = (options) => {
|
|
|
122
115
|
}
|
|
123
116
|
}
|
|
124
117
|
if (OGData.faviconURL && mergedOptions.serverCache) {
|
|
125
|
-
const filename =
|
|
118
|
+
const filename = await downloadImage({
|
|
126
119
|
directory: mergedOptions.serverCachePath,
|
|
127
120
|
url: OGData.faviconURL,
|
|
128
121
|
userAgent: mergedOptions.crawlerUserAgent
|
|
129
122
|
});
|
|
130
|
-
if (filename
|
|
123
|
+
if (filename) {
|
|
131
124
|
OGData.faviconURL = path.posix.join("/rehype-og-card", filename);
|
|
132
125
|
}
|
|
133
126
|
if (filename && mergedOptions.buildCache) {
|
|
@@ -148,11 +141,11 @@ const rehypeOGCard = (options) => {
|
|
|
148
141
|
return;
|
|
149
142
|
// eslint-disable-next-line no-magic-numbers
|
|
150
143
|
ancestors[ancestors.length - 1].children.splice(index, 1, OGCard);
|
|
151
|
-
}
|
|
144
|
+
};
|
|
152
145
|
linkCardPromises.push(linkCardPromise());
|
|
153
146
|
});
|
|
154
|
-
|
|
155
|
-
}
|
|
147
|
+
await Promise.all(linkCardPromises);
|
|
148
|
+
};
|
|
156
149
|
return transform;
|
|
157
150
|
};
|
|
158
151
|
export default rehypeOGCard;
|
package/dist/types.d.ts
CHANGED
package/dist/util/cache.d.ts
CHANGED
package/dist/util/cache.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
import { checkFileExists, copyDirectory, generateFilename } from "./file.js";
|
|
11
2
|
import fs from "fs/promises";
|
|
12
3
|
import path from "path";
|
|
@@ -15,13 +6,13 @@ import path from "path";
|
|
|
15
6
|
* @param serverCachePath Server cache path.
|
|
16
7
|
* @param buildCachePath Build cache path.
|
|
17
8
|
*/
|
|
18
|
-
const saveBuildCacheFile = (serverCachePath, buildCachePath) =>
|
|
19
|
-
const serverCacheExists =
|
|
20
|
-
const buildCacheExists =
|
|
9
|
+
const saveBuildCacheFile = async (serverCachePath, buildCachePath) => {
|
|
10
|
+
const serverCacheExists = await checkFileExists(serverCachePath);
|
|
11
|
+
const buildCacheExists = await checkFileExists(buildCachePath);
|
|
21
12
|
if (serverCacheExists && !buildCacheExists) {
|
|
22
|
-
|
|
13
|
+
await fs.copyFile(serverCachePath, buildCachePath);
|
|
23
14
|
}
|
|
24
|
-
}
|
|
15
|
+
};
|
|
25
16
|
/**
|
|
26
17
|
* Restore build cache if it exists.
|
|
27
18
|
* @param buildCachePath Build cache path.
|
|
@@ -36,30 +27,30 @@ const restoreBuildCache = (buildCachePath, serverCachePath) => {
|
|
|
36
27
|
* @param OGData OG data to save.
|
|
37
28
|
* @param buildCachePath Build cache path.
|
|
38
29
|
*/
|
|
39
|
-
const saveOGDataBuildCache = (url, OGData, buildCachePath) =>
|
|
30
|
+
const saveOGDataBuildCache = async (url, OGData, buildCachePath) => {
|
|
40
31
|
const filename = generateFilename(url, false);
|
|
41
32
|
const savePath = `${path.join(buildCachePath, filename)}.json`;
|
|
42
|
-
const cacheDirectoryExists =
|
|
33
|
+
const cacheDirectoryExists = await checkFileExists(buildCachePath);
|
|
43
34
|
if (!cacheDirectoryExists) {
|
|
44
|
-
|
|
35
|
+
await fs.mkdir(buildCachePath, { recursive: true });
|
|
45
36
|
}
|
|
46
|
-
|
|
47
|
-
}
|
|
37
|
+
await fs.writeFile(savePath, JSON.stringify(OGData));
|
|
38
|
+
};
|
|
48
39
|
/**
|
|
49
40
|
* Restore OG data from build cache.
|
|
50
41
|
* @param url URL of the OG data.
|
|
51
42
|
* @param buildCachePath Build cache path.
|
|
52
43
|
* @returns Restored OG data. If not found, returns `null`.
|
|
53
44
|
*/
|
|
54
|
-
const restoreOGDataBuildCache = (url, buildCachePath) =>
|
|
45
|
+
const restoreOGDataBuildCache = async (url, buildCachePath) => {
|
|
55
46
|
const filename = generateFilename(url, false);
|
|
56
47
|
const savePath = `${path.join(buildCachePath, filename)}.json`;
|
|
57
48
|
try {
|
|
58
|
-
const data =
|
|
49
|
+
const data = await fs.readFile(savePath, "utf-8");
|
|
59
50
|
return JSON.parse(data);
|
|
60
51
|
}
|
|
61
|
-
catch
|
|
52
|
+
catch {
|
|
62
53
|
return null;
|
|
63
54
|
}
|
|
64
|
-
}
|
|
55
|
+
};
|
|
65
56
|
export { saveBuildCacheFile, restoreBuildCache, saveOGDataBuildCache, restoreOGDataBuildCache };
|
package/dist/util/file.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
import { createHash } from "crypto";
|
|
11
2
|
import fs from "fs";
|
|
12
3
|
import path from "path";
|
|
@@ -15,15 +6,15 @@ import path from "path";
|
|
|
15
6
|
* @param filePath Path to check.
|
|
16
7
|
* @returns `true` if the file exists, `false` otherwise.
|
|
17
8
|
*/
|
|
18
|
-
const checkFileExists = (filePath) =>
|
|
9
|
+
const checkFileExists = async (filePath) => {
|
|
19
10
|
try {
|
|
20
|
-
|
|
11
|
+
await fs.promises.access(filePath);
|
|
21
12
|
return true;
|
|
22
13
|
}
|
|
23
|
-
catch
|
|
14
|
+
catch {
|
|
24
15
|
return false;
|
|
25
16
|
}
|
|
26
|
-
}
|
|
17
|
+
};
|
|
27
18
|
/**
|
|
28
19
|
* Check if the file exists.
|
|
29
20
|
* @param filePath Path to check.
|
package/dist/util/hast.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Element, ElementContent, Text } from "hast";
|
|
2
|
-
import { OGCardData, RehypeOGCardOptions } from "../types.js";
|
|
2
|
+
import type { OGCardData, RehypeOGCardOptions } from "../types.js";
|
|
3
3
|
interface AnchorElement extends Element {
|
|
4
4
|
tagName: "a";
|
|
5
5
|
properties: {
|
|
@@ -35,4 +35,4 @@ declare const createOGCard: (data: OGCardData, options: Required<{
|
|
|
35
35
|
decoding: RehypeOGCardOptions["decoding"];
|
|
36
36
|
openInNewTab: RehypeOGCardOptions["openInNewTab"];
|
|
37
37
|
}>) => Element;
|
|
38
|
-
export { AnchorElement, isTextNode, isAnchorElement, convertTextToAnchorElement, createOGCard };
|
|
38
|
+
export { type AnchorElement, isTextNode, isAnchorElement, convertTextToAnchorElement, createOGCard };
|
package/dist/util/hast.js
CHANGED
|
@@ -38,7 +38,7 @@ const createOGCard = (data, options) => {
|
|
|
38
38
|
};
|
|
39
39
|
const card = h("div.og-card-container", [
|
|
40
40
|
// eslint-disable-next-line no-extra-parens
|
|
41
|
-
h("a",
|
|
41
|
+
h("a", { href: data.url, ...(options.openInNewTab ? openInNewTab : {}) }, [
|
|
42
42
|
h("div.og-card-info", [
|
|
43
43
|
h("div.og-card-title", data.title),
|
|
44
44
|
data.description ? h("div.og-card-description", data.description) : null,
|
|
@@ -57,6 +57,7 @@ const createOGCard = (data, options) => {
|
|
|
57
57
|
data.OGImageURL
|
|
58
58
|
? h("div.og-card-image-container", [
|
|
59
59
|
h("img.og-card-image", {
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
60
61
|
alt: data.OGImageAlt || data.OGImageURL,
|
|
61
62
|
decoding: options.decoding,
|
|
62
63
|
height: data.OGImageHeight,
|
package/dist/util/network.js
CHANGED
|
@@ -1,12 +1,3 @@
|
|
|
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
1
|
import { checkFileExists, generateFilename } from "./file.js";
|
|
11
2
|
import fetch from "node-fetch";
|
|
12
3
|
import fs from "fs/promises";
|
|
@@ -27,7 +18,7 @@ const isValidURL = (url) => {
|
|
|
27
18
|
new URL(url);
|
|
28
19
|
return true;
|
|
29
20
|
}
|
|
30
|
-
catch
|
|
21
|
+
catch {
|
|
31
22
|
return false;
|
|
32
23
|
}
|
|
33
24
|
};
|
|
@@ -37,9 +28,9 @@ const isValidURL = (url) => {
|
|
|
37
28
|
* @param userAgent User agent to use for fetching.
|
|
38
29
|
* @returns OG card data.
|
|
39
30
|
*/
|
|
40
|
-
const getOGData = (url, userAgent) =>
|
|
31
|
+
const getOGData = async (url, userAgent) => {
|
|
41
32
|
try {
|
|
42
|
-
const { result } =
|
|
33
|
+
const { result } = await scraper({
|
|
43
34
|
fetchOptions: {
|
|
44
35
|
headers: {
|
|
45
36
|
"user-agent": userAgent
|
|
@@ -53,13 +44,14 @@ const getOGData = (url, userAgent) => __awaiter(void 0, void 0, void 0, function
|
|
|
53
44
|
: // eslint-disable-next-line no-undefined
|
|
54
45
|
undefined;
|
|
55
46
|
return {
|
|
56
|
-
OGImageAlt: OGImage
|
|
57
|
-
OGImageHeight: OGImage
|
|
58
|
-
OGImageURL: OGImage
|
|
59
|
-
OGImageWidth: OGImage
|
|
47
|
+
OGImageAlt: OGImage?.alt,
|
|
48
|
+
OGImageHeight: OGImage?.height,
|
|
49
|
+
OGImageURL: OGImage?.url,
|
|
50
|
+
OGImageWidth: OGImage?.width,
|
|
60
51
|
description: result.ogDescription,
|
|
61
52
|
displayURL: url,
|
|
62
53
|
faviconURL,
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
63
55
|
title: result.ogTitle || url,
|
|
64
56
|
url
|
|
65
57
|
};
|
|
@@ -69,14 +61,14 @@ const getOGData = (url, userAgent) => __awaiter(void 0, void 0, void 0, function
|
|
|
69
61
|
console.error("[rehype-og-card] Error fetching OG data:", error);
|
|
70
62
|
return null;
|
|
71
63
|
}
|
|
72
|
-
}
|
|
64
|
+
};
|
|
73
65
|
/**
|
|
74
66
|
* Download image from given URL. If the image already exists, return the filename.
|
|
75
67
|
* @param options Options to download image.
|
|
76
68
|
* @returns Filename of the downloaded image.
|
|
77
69
|
*/
|
|
78
70
|
// eslint-disable-next-line max-statements
|
|
79
|
-
const downloadImage = (options) =>
|
|
71
|
+
const downloadImage = async (options) => {
|
|
80
72
|
if (!isValidURL(options.url)) {
|
|
81
73
|
// eslint-disable-next-line no-console
|
|
82
74
|
console.error("[rehype-og-card] Invalid thumbnail URL:", options.url);
|
|
@@ -86,23 +78,23 @@ const downloadImage = (options) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
86
78
|
const filename = generateFilename(options.url);
|
|
87
79
|
const savePath = path.posix.join(options.directory, filename);
|
|
88
80
|
// If the file already exists, return the filename.
|
|
89
|
-
const fileExists =
|
|
81
|
+
const fileExists = await checkFileExists(savePath);
|
|
90
82
|
if (fileExists)
|
|
91
83
|
return filename;
|
|
92
|
-
const response =
|
|
84
|
+
const response = await fetch(options.url, {
|
|
93
85
|
headers: {
|
|
94
86
|
"user-agent": options.userAgent
|
|
95
87
|
},
|
|
96
88
|
// eslint-disable-next-line no-magic-numbers
|
|
97
89
|
signal: AbortSignal.timeout(10 * 1000)
|
|
98
90
|
});
|
|
99
|
-
const arrayBuffer =
|
|
91
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
100
92
|
const buffer = Buffer.from(arrayBuffer);
|
|
101
|
-
const saveDirectoryExists =
|
|
93
|
+
const saveDirectoryExists = await checkFileExists(options.directory);
|
|
102
94
|
if (!saveDirectoryExists) {
|
|
103
|
-
|
|
95
|
+
await fs.mkdir(options.directory, { recursive: true });
|
|
104
96
|
}
|
|
105
|
-
|
|
97
|
+
await fs.writeFile(savePath, buffer);
|
|
106
98
|
return filename;
|
|
107
99
|
}
|
|
108
100
|
catch (error) {
|
|
@@ -110,5 +102,5 @@ const downloadImage = (options) => __awaiter(void 0, void 0, void 0, function* (
|
|
|
110
102
|
console.error("[rehype-og-card] Error downloading image:", error);
|
|
111
103
|
return null;
|
|
112
104
|
}
|
|
113
|
-
}
|
|
105
|
+
};
|
|
114
106
|
export { isValidURL, getOGData, downloadImage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rehype-og-card",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
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",
|
|
@@ -42,7 +42,8 @@
|
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@changesets/changelog-github": "^0.5.0",
|
|
44
44
|
"@changesets/cli": "^2.27.7",
|
|
45
|
-
"@robot-inventor/eslint-config": "^0.
|
|
45
|
+
"@robot-inventor/eslint-config": "^4.0.0",
|
|
46
|
+
"@robot-inventor/tsconfig-base": "^1.1.0",
|
|
46
47
|
"@types/node": "^20.14.11",
|
|
47
48
|
"@types/sanitize-html": "^2.11.0",
|
|
48
49
|
"eslint": "^9.7.0",
|