rehype-og-card 3.1.13 → 3.1.15
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.js +13 -14
- package/dist/types.d.ts +4 -4
- package/dist/types.js +2 -2
- package/dist/util/cache.js +15 -20
- package/dist/util/file.js +2 -2
- package/dist/util/hast.js +1 -1
- package/dist/util/network.js +10 -10
- package/package.json +14 -17
package/dist/index.js
CHANGED
|
@@ -5,9 +5,9 @@ import { restoreBuildCache, restoreOGDataBuildCache, saveBuildCacheFile, saveOGD
|
|
|
5
5
|
import { isElement } from "hast-util-is-element";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import { visitParents } from "unist-util-visit-parents";
|
|
8
|
-
//
|
|
8
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
9
9
|
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
10
|
-
//
|
|
10
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
11
11
|
const DEFAULT_CACHE_MAX_AGE_MS = 30 * ONE_DAY_MS;
|
|
12
12
|
const DEFAULT_OPTIONS = {
|
|
13
13
|
buildCache: false,
|
|
@@ -30,9 +30,8 @@ const DEFAULT_OPTIONS = {
|
|
|
30
30
|
* @param options Plugin options.
|
|
31
31
|
* @returns Transformer function.
|
|
32
32
|
*/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
) => {
|
|
33
|
+
// oxlint-disable-next-line max-statements max-lines-per-function
|
|
34
|
+
const rehypeOGCard = (options) => {
|
|
36
35
|
const mergedOptions = {
|
|
37
36
|
...DEFAULT_OPTIONS,
|
|
38
37
|
...options
|
|
@@ -55,10 +54,10 @@ const rehypeOGCard = (options
|
|
|
55
54
|
* Transform function to create OG card from bare links.
|
|
56
55
|
* @param tree Root node of the HAST tree.
|
|
57
56
|
*/
|
|
58
|
-
//
|
|
57
|
+
// oxlint-disable-next-line max-lines-per-function
|
|
59
58
|
const transform = async (tree) => {
|
|
60
59
|
const linkCardPromises = [];
|
|
61
|
-
//
|
|
60
|
+
// oxlint-disable-next-line max-statements, max-lines-per-function
|
|
62
61
|
visitParents(tree, ["element", "text"], (node, ancestors) => {
|
|
63
62
|
let anchorNode = null;
|
|
64
63
|
const isBareLink = node.type === "text" && isValidURL(node.value.trim());
|
|
@@ -68,7 +67,7 @@ const rehypeOGCard = (options
|
|
|
68
67
|
}
|
|
69
68
|
else if (mergedOptions.enableSameTextURLConversion && isValidAnchor) {
|
|
70
69
|
const isSameTextURL =
|
|
71
|
-
//
|
|
70
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
72
71
|
node.children.length === 1 &&
|
|
73
72
|
node.children[0] &&
|
|
74
73
|
isTextNode(node.children[0]) &&
|
|
@@ -80,7 +79,7 @@ const rehypeOGCard = (options
|
|
|
80
79
|
else {
|
|
81
80
|
return;
|
|
82
81
|
}
|
|
83
|
-
//
|
|
82
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
84
83
|
const parent = ancestors[ancestors.length - 1];
|
|
85
84
|
if (!parent)
|
|
86
85
|
return;
|
|
@@ -89,14 +88,14 @@ const rehypeOGCard = (options
|
|
|
89
88
|
const shouldSkip = ancestors.some((ancestor) => isElement(ancestor) && ["ul", "ol"].includes(ancestor.tagName));
|
|
90
89
|
if (shouldSkip)
|
|
91
90
|
return;
|
|
92
|
-
//
|
|
91
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
93
92
|
const isTheOnlyChild = parent.children.length === 1;
|
|
94
93
|
if (!isTheOnlyChild)
|
|
95
94
|
return;
|
|
96
95
|
const targetURL = new URL(anchorNode.properties.href);
|
|
97
96
|
if (mergedOptions.excludeDomains.includes(targetURL.hostname))
|
|
98
97
|
return;
|
|
99
|
-
//
|
|
98
|
+
// oxlint-disable-next-line max-statements max-lines-per-function jsdoc-js/require-jsdoc
|
|
100
99
|
const linkCardPromise = async () => {
|
|
101
100
|
let OGData = mergedOptions.buildCache
|
|
102
101
|
? await restoreOGDataBuildCache(anchorNode.properties.href, mergedOptions.buildCachePath, mergedOptions.buildCacheMaxAge)
|
|
@@ -153,16 +152,16 @@ const rehypeOGCard = (options
|
|
|
153
152
|
}
|
|
154
153
|
const OGCard = createOGCard(OGData, mergedOptions);
|
|
155
154
|
const { removeParentPTag: shouldRemoveParentPTag } = mergedOptions;
|
|
156
|
-
//
|
|
155
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
157
156
|
const replacementContainer = shouldRemoveParentPTag ? ancestors[ancestors.length - 2] : parent;
|
|
158
157
|
const replacementTarget = shouldRemoveParentPTag ? parent : node;
|
|
159
158
|
if (!replacementContainer || !("children" in replacementContainer))
|
|
160
159
|
return;
|
|
161
160
|
const index = replacementContainer.children.indexOf(replacementTarget);
|
|
162
|
-
//
|
|
161
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
163
162
|
if (index === -1)
|
|
164
163
|
return;
|
|
165
|
-
//
|
|
164
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
166
165
|
replacementContainer.children.splice(index, 1, OGCard);
|
|
167
166
|
};
|
|
168
167
|
linkCardPromises.push(linkCardPromise());
|
package/dist/types.d.ts
CHANGED
|
@@ -89,10 +89,10 @@ interface OGCardData {
|
|
|
89
89
|
OGImageWidth?: number | undefined;
|
|
90
90
|
OGImageHeight?: number | undefined;
|
|
91
91
|
}
|
|
92
|
-
declare const
|
|
92
|
+
declare const parseCacheIndex: import("arktype/internal/variants/object.ts").ObjectType<(In: string) => import("arktype/internal/attributes.ts").To<{
|
|
93
93
|
[x: string]: {
|
|
94
94
|
createdAt: number;
|
|
95
95
|
};
|
|
96
|
-
}
|
|
97
|
-
type CacheIndex = typeof
|
|
98
|
-
export {
|
|
96
|
+
}>, {}>;
|
|
97
|
+
type CacheIndex = typeof parseCacheIndex.infer;
|
|
98
|
+
export { parseCacheIndex, type RehypeOGCardOptions, type OGCardData, type CacheIndex };
|
package/dist/types.js
CHANGED
package/dist/util/cache.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/*
|
|
2
|
-
import {
|
|
1
|
+
/* oxlint-disable max-lines */
|
|
2
|
+
import { parseCacheIndex } from "../types.js";
|
|
3
3
|
import { checkFileExists, generateFilename } from "./file.js";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
import fsSync from "fs";
|
|
@@ -36,10 +36,10 @@ const getCacheIndexLockPath = (directory) => path.join(directory, CACHE_INDEX_LO
|
|
|
36
36
|
* @param ms Duration in milliseconds.
|
|
37
37
|
*/
|
|
38
38
|
const sleepSync = (ms) => {
|
|
39
|
-
//
|
|
39
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
40
40
|
const buffer = new SharedArrayBuffer(4);
|
|
41
41
|
const view = new Int32Array(buffer);
|
|
42
|
-
//
|
|
42
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
43
43
|
Atomics.wait(view, 0, 0, ms);
|
|
44
44
|
};
|
|
45
45
|
/**
|
|
@@ -123,8 +123,7 @@ const readCacheIndexSync = (directory) => {
|
|
|
123
123
|
const indexPath = getCacheIndexPath(directory);
|
|
124
124
|
try {
|
|
125
125
|
const raw = fsSync.readFileSync(indexPath, "utf-8");
|
|
126
|
-
const
|
|
127
|
-
const validated = cacheIndexSchema(parsed);
|
|
126
|
+
const validated = parseCacheIndex(raw);
|
|
128
127
|
if (validated instanceof type.errors)
|
|
129
128
|
return {};
|
|
130
129
|
return validated;
|
|
@@ -162,8 +161,7 @@ const readCacheIndex = async (directory) => {
|
|
|
162
161
|
return {};
|
|
163
162
|
try {
|
|
164
163
|
const raw = await fs.readFile(indexPath, "utf-8");
|
|
165
|
-
const
|
|
166
|
-
const validated = cacheIndexSchema(parsed);
|
|
164
|
+
const validated = parseCacheIndex(raw);
|
|
167
165
|
if (validated instanceof type.errors)
|
|
168
166
|
return {};
|
|
169
167
|
return validated;
|
|
@@ -203,7 +201,7 @@ const removeCacheEntrySync = (directory, filename) => {
|
|
|
203
201
|
* @param directory Cache directory path.
|
|
204
202
|
* @param maxAgeMs Cache expiration time in milliseconds.
|
|
205
203
|
*/
|
|
206
|
-
//
|
|
204
|
+
// oxlint-disable-next-line max-statements, max-lines-per-function
|
|
207
205
|
const pruneExpiredCacheFilesSync = (directory, maxAgeMs) => {
|
|
208
206
|
if (maxAgeMs === false)
|
|
209
207
|
return;
|
|
@@ -216,10 +214,10 @@ const pruneExpiredCacheFilesSync = (directory, maxAgeMs) => {
|
|
|
216
214
|
const entryPath = path.join(directory, entry.name);
|
|
217
215
|
if (entry.isDirectory()) {
|
|
218
216
|
pruneExpiredCacheFilesSync(entryPath, maxAgeMs);
|
|
219
|
-
//
|
|
217
|
+
// oxlint-disable-next-line no-continue
|
|
220
218
|
continue;
|
|
221
219
|
}
|
|
222
|
-
//
|
|
220
|
+
// oxlint-disable-next-line no-continue
|
|
223
221
|
if (entry.name === CACHE_INDEX_FILENAME || entry.name === CACHE_INDEX_LOCK_FILENAME)
|
|
224
222
|
continue;
|
|
225
223
|
if (entry.name.endsWith(".json")) {
|
|
@@ -228,7 +226,7 @@ const pruneExpiredCacheFilesSync = (directory, maxAgeMs) => {
|
|
|
228
226
|
const data = JSON.parse(raw);
|
|
229
227
|
if (typeof data.cachedAt !== "number") {
|
|
230
228
|
fsSync.rmSync(entryPath, { force: true });
|
|
231
|
-
//
|
|
229
|
+
// oxlint-disable-next-line no-continue
|
|
232
230
|
continue;
|
|
233
231
|
}
|
|
234
232
|
if (isCacheExpired(data.cachedAt, maxAgeMs)) {
|
|
@@ -238,14 +236,14 @@ const pruneExpiredCacheFilesSync = (directory, maxAgeMs) => {
|
|
|
238
236
|
catch {
|
|
239
237
|
fsSync.rmSync(entryPath, { force: true });
|
|
240
238
|
}
|
|
241
|
-
//
|
|
239
|
+
// oxlint-disable-next-line no-continue
|
|
242
240
|
continue;
|
|
243
241
|
}
|
|
244
242
|
const cachedAtMs = index[entry.name]?.createdAt;
|
|
245
243
|
if (typeof cachedAtMs !== "number" || isCacheExpired(cachedAtMs, maxAgeMs)) {
|
|
246
244
|
removeCacheEntrySync(directory, entry.name);
|
|
247
245
|
indexUpdated = true;
|
|
248
|
-
//
|
|
246
|
+
// oxlint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
249
247
|
delete index[entry.name];
|
|
250
248
|
}
|
|
251
249
|
}
|
|
@@ -258,7 +256,7 @@ const pruneExpiredCacheFilesSync = (directory, maxAgeMs) => {
|
|
|
258
256
|
* @param serverCachePath Server cache path.
|
|
259
257
|
* @param buildCachePath Build cache path.
|
|
260
258
|
*/
|
|
261
|
-
//
|
|
259
|
+
// oxlint-disable-next-line max-statements
|
|
262
260
|
const saveBuildCacheFile = async (serverCachePath, buildCachePath) => {
|
|
263
261
|
const serverCacheExists = await checkFileExists(serverCachePath);
|
|
264
262
|
const buildCacheExists = await checkFileExists(buildCachePath);
|
|
@@ -315,9 +313,8 @@ const saveOGDataBuildCache = async (url, OGData, buildCachePath) => {
|
|
|
315
313
|
* @param maxAgeMs Cache expiration time in milliseconds.
|
|
316
314
|
* @returns Restored OG data. If not found, returns `null`.
|
|
317
315
|
*/
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
) => {
|
|
316
|
+
// oxlint-disable-next-line max-statements
|
|
317
|
+
const restoreOGDataBuildCache = async (url, buildCachePath, maxAgeMs) => {
|
|
321
318
|
const filename = generateFilename(url, false);
|
|
322
319
|
const savePath = `${path.join(buildCachePath, filename)}.json`;
|
|
323
320
|
try {
|
|
@@ -325,7 +322,6 @@ const restoreOGDataBuildCache = async (url, buildCachePath, maxAgeMs
|
|
|
325
322
|
const parsed = JSON.parse(data);
|
|
326
323
|
if (typeof parsed.cachedAt !== "number") {
|
|
327
324
|
if (maxAgeMs === false) {
|
|
328
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
329
325
|
const { cachedAt: __, ...rest } = parsed;
|
|
330
326
|
return rest;
|
|
331
327
|
}
|
|
@@ -336,7 +332,6 @@ const restoreOGDataBuildCache = async (url, buildCachePath, maxAgeMs
|
|
|
336
332
|
await fs.rm(savePath, { force: true });
|
|
337
333
|
return null;
|
|
338
334
|
}
|
|
339
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
340
335
|
const { cachedAt: __, ...rest } = parsed;
|
|
341
336
|
return rest;
|
|
342
337
|
}
|
package/dist/util/file.js
CHANGED
|
@@ -34,7 +34,7 @@ const checkFileExistsSync = (filePath) => {
|
|
|
34
34
|
const createDirectorySync = (dir) => {
|
|
35
35
|
fs.mkdir(dir, { recursive: true }, (error) => {
|
|
36
36
|
if (error) {
|
|
37
|
-
//
|
|
37
|
+
// oxlint-disable-next-line no-console
|
|
38
38
|
console.error("[rehype-og-card] Failed to copy files: ", error);
|
|
39
39
|
}
|
|
40
40
|
});
|
|
@@ -54,7 +54,7 @@ const copyDirectory = (source, destination) => {
|
|
|
54
54
|
}
|
|
55
55
|
fs.cp(source, destination, { recursive: true }, (error) => {
|
|
56
56
|
if (error) {
|
|
57
|
-
//
|
|
57
|
+
// oxlint-disable-next-line no-console
|
|
58
58
|
console.error("[rehype-og-card] Failed to copy files: ", error);
|
|
59
59
|
}
|
|
60
60
|
});
|
package/dist/util/hast.js
CHANGED
|
@@ -58,7 +58,7 @@ const createOGCard = (data, options) => {
|
|
|
58
58
|
data.OGImageURL
|
|
59
59
|
? h("div.og-card-image-container", [
|
|
60
60
|
h("img.og-card-image", {
|
|
61
|
-
//
|
|
61
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
62
62
|
alt: data.OGImageAlt || data.OGImageURL,
|
|
63
63
|
decoding: options.decoding,
|
|
64
64
|
height: data.OGImageHeight,
|
package/dist/util/network.js
CHANGED
|
@@ -21,11 +21,11 @@ const isValidURL = (url) => {
|
|
|
21
21
|
* @returns Resolved absolute URL or `undefined` if the raw URL is invalid.
|
|
22
22
|
*/
|
|
23
23
|
const resolveAbsoluteURL = (rawURL, baseURL) => {
|
|
24
|
-
//
|
|
24
|
+
// oxlint-disable-next-line no-undefined
|
|
25
25
|
if (!rawURL)
|
|
26
26
|
return undefined;
|
|
27
27
|
const parsedUrl = URL.parse(rawURL, baseURL);
|
|
28
|
-
//
|
|
28
|
+
// oxlint-disable-next-line no-undefined
|
|
29
29
|
return parsedUrl ? parsedUrl.href : undefined;
|
|
30
30
|
};
|
|
31
31
|
/**
|
|
@@ -56,13 +56,13 @@ const getOGData = async (url, userAgent) => {
|
|
|
56
56
|
description: result.ogDescription,
|
|
57
57
|
displayURL: url,
|
|
58
58
|
faviconURL,
|
|
59
|
-
//
|
|
59
|
+
// oxlint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
|
60
60
|
title: result.ogTitle || url,
|
|
61
61
|
url
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
catch (error) {
|
|
65
|
-
//
|
|
65
|
+
// oxlint-disable-next-line no-console
|
|
66
66
|
console.error("[rehype-og-card] Error fetching OG data:", error);
|
|
67
67
|
return null;
|
|
68
68
|
}
|
|
@@ -72,10 +72,10 @@ const getOGData = async (url, userAgent) => {
|
|
|
72
72
|
* @param options Options to download image.
|
|
73
73
|
* @returns Filename of the downloaded image.
|
|
74
74
|
*/
|
|
75
|
-
//
|
|
75
|
+
// oxlint-disable-next-line max-statements, max-lines-per-function
|
|
76
76
|
const downloadImage = async (options) => {
|
|
77
77
|
if (!isValidURL(options.url)) {
|
|
78
|
-
//
|
|
78
|
+
// oxlint-disable-next-line no-console
|
|
79
79
|
console.error("[rehype-og-card] Invalid thumbnail URL:", options.url);
|
|
80
80
|
return null;
|
|
81
81
|
}
|
|
@@ -94,7 +94,7 @@ const downloadImage = async (options) => {
|
|
|
94
94
|
if (typeof cachedAt === "number" && !isCacheExpired(cachedAt, maxAge))
|
|
95
95
|
return filename;
|
|
96
96
|
if (typeof entry !== "undefined") {
|
|
97
|
-
//
|
|
97
|
+
// oxlint-disable-next-line @typescript-eslint/no-dynamic-delete
|
|
98
98
|
delete index[filename];
|
|
99
99
|
await writeCacheIndex(options.directory, index);
|
|
100
100
|
}
|
|
@@ -104,7 +104,7 @@ const downloadImage = async (options) => {
|
|
|
104
104
|
headers: {
|
|
105
105
|
"user-agent": options.userAgent
|
|
106
106
|
},
|
|
107
|
-
//
|
|
107
|
+
// oxlint-disable-next-line no-magic-numbers
|
|
108
108
|
signal: AbortSignal.timeout(10 * 1000)
|
|
109
109
|
});
|
|
110
110
|
if (!response.ok)
|
|
@@ -112,7 +112,7 @@ const downloadImage = async (options) => {
|
|
|
112
112
|
const contentType = response.headers.get("content-type");
|
|
113
113
|
const normalizedContentType = contentType?.split(";")[0]?.trim().toLowerCase();
|
|
114
114
|
if (!normalizedContentType?.startsWith("image/")) {
|
|
115
|
-
//
|
|
115
|
+
// oxlint-disable-next-line no-console
|
|
116
116
|
console.error("[rehype-og-card] Invalid image content type:", contentType ?? "unknown", "for", options.url);
|
|
117
117
|
return null;
|
|
118
118
|
}
|
|
@@ -130,7 +130,7 @@ const downloadImage = async (options) => {
|
|
|
130
130
|
return filename;
|
|
131
131
|
}
|
|
132
132
|
catch (error) {
|
|
133
|
-
//
|
|
133
|
+
// oxlint-disable-next-line no-console
|
|
134
134
|
console.error("[rehype-og-card] Error downloading image:", error);
|
|
135
135
|
return null;
|
|
136
136
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rehype-og-card",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.15",
|
|
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",
|
|
@@ -12,11 +12,11 @@
|
|
|
12
12
|
"provenance": true
|
|
13
13
|
},
|
|
14
14
|
"scripts": {
|
|
15
|
-
"test": "
|
|
16
|
-
"lint": "
|
|
17
|
-
"format": "
|
|
18
|
-
"format:check": "
|
|
19
|
-
"build": "
|
|
15
|
+
"test": "vitest",
|
|
16
|
+
"lint": "oxlint ./src",
|
|
17
|
+
"format": "prettier --write ./src/**/*",
|
|
18
|
+
"format:check": "prettier --check ./src/**/*",
|
|
19
|
+
"build": "tsc",
|
|
20
20
|
"version": "npm run build && git add .",
|
|
21
21
|
"ci:version": "changeset version && npm run version",
|
|
22
22
|
"ci:publish": "npm run build && changeset publish"
|
|
@@ -40,20 +40,20 @@
|
|
|
40
40
|
},
|
|
41
41
|
"homepage": "https://github.com/Robot-Inventor/rehype-og-card#readme",
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@changesets/changelog-github": "^0.
|
|
44
|
-
"@changesets/cli": "^
|
|
45
|
-
"@robot-inventor/
|
|
46
|
-
"@robot-inventor/tsconfig-base": "^7.
|
|
47
|
-
"@types/node": "^25.9.
|
|
48
|
-
"eslint": "^10.7.0",
|
|
43
|
+
"@changesets/changelog-github": "^1.0.1",
|
|
44
|
+
"@changesets/cli": "^3.0.3",
|
|
45
|
+
"@robot-inventor/oxlint-config": "^0.2.0",
|
|
46
|
+
"@robot-inventor/tsconfig-base": "^7.2.0",
|
|
47
|
+
"@types/node": "^25.9.6",
|
|
49
48
|
"hast-util-to-html": "^9.0.5",
|
|
49
|
+
"oxlint": "^1.83.0",
|
|
50
50
|
"prettier": "^3.9.6",
|
|
51
51
|
"rehype-stringify": "^10.0.1",
|
|
52
52
|
"remark-parse": "^11.0.0",
|
|
53
53
|
"remark-rehype": "^11.1.2",
|
|
54
|
-
"typescript": "^
|
|
54
|
+
"typescript": "^7.0.2",
|
|
55
55
|
"unified": "^11.0.5",
|
|
56
|
-
"vitest": "^
|
|
56
|
+
"vitest": "^5.0.0"
|
|
57
57
|
},
|
|
58
58
|
"dependencies": {
|
|
59
59
|
"@types/hast": "^3.0.5",
|
|
@@ -63,8 +63,5 @@
|
|
|
63
63
|
"node-fetch": "^3.3.2",
|
|
64
64
|
"open-graph-scraper": "^6.12.0",
|
|
65
65
|
"unist-util-visit-parents": "^6.0.2"
|
|
66
|
-
},
|
|
67
|
-
"overrides": {
|
|
68
|
-
"zod-validation-error": "^5.0.0"
|
|
69
66
|
}
|
|
70
67
|
}
|