nuxt-og-image 6.8.0 → 6.9.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/dist/chunks/tw4.mjs +1 -1
- package/dist/chunks/uno.mjs +2 -2
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/server/nitro.d.ts +9 -0
- package/dist/runtime/server/nitro.js +10 -0
- package/dist/runtime/server/og-image/bindings/fetch/node.d.ts +2 -0
- package/dist/runtime/server/og-image/bindings/fetch/node.js +42 -0
- package/dist/runtime/server/og-image/bindings/fetch/web.d.ts +7 -0
- package/dist/runtime/server/og-image/bindings/fetch/web.js +3 -0
- package/dist/runtime/server/og-image/bindings/font-assets/external-url.d.ts +0 -11
- package/dist/runtime/server/og-image/bindings/font-assets/external-url.js +2 -19
- package/dist/runtime/server/og-image/browser/screenshot.js +4 -1
- package/dist/runtime/server/og-image/context.js +9 -1
- package/dist/runtime/server/routes/resolve.js +2 -2
- package/dist/runtime/server/util/cloudflare.js +2 -2
- package/dist/runtime/server/util/ssrf.d.ts +31 -0
- package/dist/runtime/server/util/ssrf.js +64 -5
- package/dist/runtime/server/utils.d.ts +1 -1
- package/dist/runtime/server/utils.js +3 -3
- package/dist/runtime/types.d.ts +3 -1
- package/dist/shared/{nuxt-og-image.D936Fp6O.mjs → nuxt-og-image.CTD0ZPNd.mjs} +49 -26
- package/package.json +38 -41
- package/types/virtual.d.ts +8 -0
package/dist/chunks/tw4.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { resolveModulePath } from 'exsolve';
|
|
4
4
|
import { dirname, join } from 'pathe';
|
|
5
|
-
import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.
|
|
5
|
+
import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.CTD0ZPNd.mjs';
|
|
6
6
|
import 'node:crypto';
|
|
7
7
|
import 'node:fs';
|
|
8
8
|
import '@nuxt/kit';
|
package/dist/chunks/uno.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { defu } from 'defu';
|
|
3
3
|
import { logger } from '../../dist/runtime/logger.js';
|
|
4
|
-
import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.
|
|
5
|
-
export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.
|
|
4
|
+
import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.CTD0ZPNd.mjs';
|
|
5
|
+
export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.CTD0ZPNd.mjs';
|
|
6
6
|
import 'node:crypto';
|
|
7
7
|
import 'node:fs';
|
|
8
8
|
import 'node:url';
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -10,7 +10,7 @@ import 'nuxtseo-shared/kit';
|
|
|
10
10
|
import 'pathe';
|
|
11
11
|
import 'pkg-types';
|
|
12
12
|
import 'std-env';
|
|
13
|
-
export { n as default } from './shared/nuxt-og-image.
|
|
13
|
+
export { n as default } from './shared/nuxt-og-image.CTD0ZPNd.mjs';
|
|
14
14
|
import '../dist/runtime/logger.js';
|
|
15
15
|
import 'magic-string';
|
|
16
16
|
import 'oxc-walker';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { H3Event } from '#nuxtseo/h3';
|
|
2
|
+
import type { OgImageOptionsInternal } from '../types.js';
|
|
3
|
+
type Options = Partial<OgImageOptionsInternal>;
|
|
4
|
+
/**
|
|
5
|
+
* Build the absolute OG image URL for a page. Without a page path, uses the current request path.
|
|
6
|
+
*/
|
|
7
|
+
export declare function getOgImageUrl(event: H3Event, options?: Options): string;
|
|
8
|
+
export declare function getOgImageUrl(event: H3Event, pagePath: string, options?: Options): string;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { parseURL, withoutBase } from "ufo";
|
|
2
|
+
import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
3
|
+
import { withSiteUrl } from "#site-config/server/composables/utils";
|
|
4
|
+
import { getOgImagePath } from "./utils.js";
|
|
5
|
+
export function getOgImageUrl(event, pathOrOptions, maybeOptions) {
|
|
6
|
+
const pagePath = typeof pathOrOptions === "string" ? pathOrOptions : withoutBase(parseURL(event.path).pathname, useRuntimeConfig(event).app.baseURL) || "/";
|
|
7
|
+
const options = typeof pathOrOptions === "string" ? maybeOptions : pathOrOptions;
|
|
8
|
+
const { path } = getOgImagePath(event, pagePath, options);
|
|
9
|
+
return withSiteUrl(event, path, { canonical: !import.meta.dev });
|
|
10
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { lookup as dnsLookup } from "node:dns";
|
|
2
|
+
import { request as httpRequest } from "node:http";
|
|
3
|
+
import { request as httpsRequest } from "node:https";
|
|
4
|
+
import { Readable } from "node:stream";
|
|
5
|
+
function guardedLookup(isBlocked) {
|
|
6
|
+
return (hostname, options, callback) => {
|
|
7
|
+
dnsLookup(hostname, { ...options, all: true }, (err, addresses) => {
|
|
8
|
+
if (err)
|
|
9
|
+
return callback(err, "", 0);
|
|
10
|
+
const blocked = addresses.find((a) => isBlocked(a.address));
|
|
11
|
+
if (blocked)
|
|
12
|
+
return callback(new Error(`[Nuxt OG Image] ${hostname} resolves to blocked address ${blocked.address}.`), "", 0);
|
|
13
|
+
if (options.all)
|
|
14
|
+
return callback(null, addresses);
|
|
15
|
+
callback(null, addresses[0].address, addresses[0].family);
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export function guardedFetch(url, { headers, signal, isBlockedAddress }) {
|
|
20
|
+
const request = url.startsWith("https:") ? httpsRequest : httpRequest;
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const req = request(url, {
|
|
23
|
+
// Match the defaults that `fetch` sent before this transport existed.
|
|
24
|
+
headers: { "accept": "*/*", "user-agent": "node", ...headers },
|
|
25
|
+
signal,
|
|
26
|
+
lookup: guardedLookup(isBlockedAddress ?? (() => false)),
|
|
27
|
+
// A fresh socket per hop: never reuse a connection nobody checked.
|
|
28
|
+
agent: false
|
|
29
|
+
}, (res) => {
|
|
30
|
+
const status = res.statusCode ?? 0;
|
|
31
|
+
const responseHeaders = new Headers();
|
|
32
|
+
for (const [name, value] of Object.entries(res.headers)) {
|
|
33
|
+
for (const v of [value ?? []].flat())
|
|
34
|
+
responseHeaders.append(name, v);
|
|
35
|
+
}
|
|
36
|
+
const body = status === 204 || status === 304 ? null : Readable.toWeb(res);
|
|
37
|
+
resolve(new Response(body, { status, headers: responseHeaders }));
|
|
38
|
+
});
|
|
39
|
+
req.on("error", reject);
|
|
40
|
+
req.end();
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface GuardedFetchInit {
|
|
2
|
+
headers?: Record<string, string>;
|
|
3
|
+
signal: AbortSignal;
|
|
4
|
+
/** Rejects the connection when a resolved address matches. Node only. */
|
|
5
|
+
isBlockedAddress?: (address: string) => boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function guardedFetch(url: string, { headers, signal }: GuardedFetchInit): Promise<Response>;
|
|
@@ -12,17 +12,6 @@ export declare function isDataFontUrl(path: string): boolean;
|
|
|
12
12
|
* Check `isDataFontUrl` first — `data:` is non-http and would report true here.
|
|
13
13
|
*/
|
|
14
14
|
export declare function isExternalFontUrl(path: string): boolean;
|
|
15
|
-
/**
|
|
16
|
-
* Resolve an authority-bearing font URL to the href to fetch, but only when it
|
|
17
|
-
* is same-origin with the configured site URL. Returns null otherwise.
|
|
18
|
-
*
|
|
19
|
-
* Runtime external font URLs are unsupported — `@nuxt/fonts` is the only
|
|
20
|
-
* supported way to load custom fonts, and it serves them same-origin. The single
|
|
21
|
-
* allowed exception is the site's own origin, gated on an explicitly configured
|
|
22
|
-
* site URL. Resolving against that origin also collapses canonicalization
|
|
23
|
-
* bypasses (a crafted " //127.0.0.1" resolves cross-origin → rejected).
|
|
24
|
-
*/
|
|
25
|
-
export declare function resolveSameOriginFontUrl(path: string, siteUrl: string | undefined): string | null;
|
|
26
15
|
/**
|
|
27
16
|
* Fetch a `data:` or authority-bearing font URL. `data:` is decoded directly
|
|
28
17
|
* (no network). An external URL is fetched only when same-origin with the site
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchWithRedirectValidation } from "../../../util/ssrf.js";
|
|
1
|
+
import { fetchWithRedirectValidation, resolveSameOriginUrl } from "../../../util/ssrf.js";
|
|
2
2
|
const SENTINEL_ORIGIN = "http://font-asset.invalid/";
|
|
3
3
|
const SENTINEL_HOST = "font-asset.invalid";
|
|
4
4
|
export function isDataFontUrl(path) {
|
|
@@ -13,23 +13,6 @@ export function isExternalFontUrl(path) {
|
|
|
13
13
|
}
|
|
14
14
|
return resolved.protocol !== "http:" || resolved.host !== SENTINEL_HOST;
|
|
15
15
|
}
|
|
16
|
-
export function resolveSameOriginFontUrl(path, siteUrl) {
|
|
17
|
-
if (!siteUrl)
|
|
18
|
-
return null;
|
|
19
|
-
let siteOrigin;
|
|
20
|
-
try {
|
|
21
|
-
siteOrigin = new URL(siteUrl).origin;
|
|
22
|
-
} catch {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
let target;
|
|
26
|
-
try {
|
|
27
|
-
target = new URL(path, siteOrigin);
|
|
28
|
-
} catch {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
return target.origin === siteOrigin ? target.href : null;
|
|
32
|
-
}
|
|
33
16
|
export async function fetchSpecialFontUrl(path, siteUrl, timeout) {
|
|
34
17
|
if (isDataFontUrl(path)) {
|
|
35
18
|
const res = await fetch(path.trimStart(), { signal: AbortSignal.timeout(timeout) }).catch(() => {
|
|
@@ -39,7 +22,7 @@ export async function fetchSpecialFontUrl(path, siteUrl, timeout) {
|
|
|
39
22
|
return Buffer.from(await res.arrayBuffer());
|
|
40
23
|
throw new Error("[Nuxt OG Image] Invalid data: font URL.");
|
|
41
24
|
}
|
|
42
|
-
const href =
|
|
25
|
+
const href = resolveSameOriginUrl(path, siteUrl);
|
|
43
26
|
if (!href)
|
|
44
27
|
throw new Error("[Nuxt OG Image] External font URLs are not supported. Load custom fonts via @nuxt/fonts.");
|
|
45
28
|
const ab = await fetchWithRedirectValidation(href, { timeout });
|
|
@@ -5,6 +5,7 @@ import { getNitroOrigin } from "#site-config/server/composables";
|
|
|
5
5
|
import { buildOgImageUrl } from "../../../shared.js";
|
|
6
6
|
import { getFetchTimeout } from "../../util/fetchTimeout.js";
|
|
7
7
|
import { logger } from "../../util/logger.js";
|
|
8
|
+
import { resolveSameOriginUrl } from "../../util/ssrf.js";
|
|
8
9
|
import { useOgImageRuntimeConfig } from "../../utils.js";
|
|
9
10
|
function isPlaywrightPage(page) {
|
|
10
11
|
return typeof page.setViewportSize === "function";
|
|
@@ -101,7 +102,9 @@ export async function createScreenshot({ basePath, e, options, extension, timing
|
|
|
101
102
|
await page.setContent(html, { waitUntil: "networkidle0" });
|
|
102
103
|
}
|
|
103
104
|
} else {
|
|
104
|
-
const url =
|
|
105
|
+
const url = resolveSameOriginUrl(withQuery(path, options.props || {}), getNitroOrigin(e));
|
|
106
|
+
if (!url)
|
|
107
|
+
throw new Error("[Nuxt OG Image] The browser renderer can only navigate to a same-origin path.");
|
|
105
108
|
await gotoWithIdle(page, url, 1e4);
|
|
106
109
|
}
|
|
107
110
|
const screenshotOptions = {
|
|
@@ -13,6 +13,7 @@ import { autoEjectCommunityTemplate } from "../util/auto-eject.js";
|
|
|
13
13
|
import { createNitroRouteRuleMatcher } from "../util/kit.js";
|
|
14
14
|
import { normaliseOptions } from "../util/options.js";
|
|
15
15
|
import { getEventQuery } from "../util/query.js";
|
|
16
|
+
import { parseSameOriginPath } from "../util/ssrf.js";
|
|
16
17
|
import { createTimings, TIMING_CTX_KEY } from "../util/timings.js";
|
|
17
18
|
import { withTimeout } from "../util/withTimeout.js";
|
|
18
19
|
import { useOgImageRuntimeConfig } from "../utils.js";
|
|
@@ -120,7 +121,14 @@ export async function resolveContext(e) {
|
|
|
120
121
|
delete queryParams.html;
|
|
121
122
|
queryParams = separateProps(queryParams);
|
|
122
123
|
}
|
|
123
|
-
const
|
|
124
|
+
const rawPath = urlOptions._path ?? "/";
|
|
125
|
+
const basePath = parseSameOriginPath(typeof rawPath === "string" ? withoutTrailingSlash(rawPath) : rawPath);
|
|
126
|
+
if (!basePath) {
|
|
127
|
+
return createError({
|
|
128
|
+
statusCode: 400,
|
|
129
|
+
statusMessage: "[Nuxt OG Image] The _path option must be a same-origin path."
|
|
130
|
+
});
|
|
131
|
+
}
|
|
124
132
|
const componentHash = urlOptions._componentHash || "";
|
|
125
133
|
delete urlOptions._path;
|
|
126
134
|
delete urlOptions._hash;
|
|
@@ -4,6 +4,7 @@ import { fetchWithEvent } from "#nuxtseo/nitro";
|
|
|
4
4
|
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
5
5
|
import { isInternalRoute } from "../../shared.js";
|
|
6
6
|
import { getEventQuery } from "../util/query.js";
|
|
7
|
+
import { parseSameOriginPath } from "../util/ssrf.js";
|
|
7
8
|
import { useOgImageRuntimeConfig } from "../utils.js";
|
|
8
9
|
const RE_META_TAG = /<meta\b[^>]*>/gi;
|
|
9
10
|
const RE_META_KEY = /\b(?:property|name)\s*=\s*(?:"([^"]+)"|'([^']+)')/i;
|
|
@@ -11,7 +12,6 @@ const RE_META_CONTENT = /\bcontent\s*=\s*(?:"([^"]*)"|'([^']*)')/i;
|
|
|
11
12
|
const MAX_RESOLVE_PATH_LENGTH = 2048;
|
|
12
13
|
const RE_STRIP_PREFIX = /^.*?\/_og\/r/;
|
|
13
14
|
const RE_IMAGE_EXT = /\.(?:png|jpe?g|webp|svg)$/i;
|
|
14
|
-
const RE_DOUBLE_LEADING_SLASH = /^\/{2,}/;
|
|
15
15
|
function extractMeta(html, key) {
|
|
16
16
|
for (const tagMatch of html.matchAll(RE_META_TAG)) {
|
|
17
17
|
const tag = tagMatch[0];
|
|
@@ -78,7 +78,7 @@ export default defineEventHandler(async (event) => {
|
|
|
78
78
|
statusMessage: `[Nuxt OG Image] Target path exceeds ${MAX_RESOLVE_PATH_LENGTH} characters.`
|
|
79
79
|
});
|
|
80
80
|
}
|
|
81
|
-
if (targetPath.includes("://") ||
|
|
81
|
+
if (targetPath.includes("://") || !parseSameOriginPath(targetPath)) {
|
|
82
82
|
throw createError({
|
|
83
83
|
statusCode: 400,
|
|
84
84
|
statusMessage: "[Nuxt OG Image] Target path must be a same-origin path."
|
|
@@ -7,11 +7,11 @@ export function getCloudflareEnv(event) {
|
|
|
7
7
|
const runtimeEnv = toRecord(runtimeCloudflare?.env);
|
|
8
8
|
if (runtimeEnv)
|
|
9
9
|
return runtimeEnv;
|
|
10
|
-
const contextCloudflare = toRecord(event?.context
|
|
10
|
+
const contextCloudflare = toRecord(event?.context?.cloudflare);
|
|
11
11
|
const contextEnv = toRecord(contextCloudflare?.env);
|
|
12
12
|
if (contextEnv)
|
|
13
13
|
return contextEnv;
|
|
14
|
-
const platform = toRecord(event?.context
|
|
14
|
+
const platform = toRecord(event?.context?._platform);
|
|
15
15
|
const platformCloudflare = toRecord(platform?.cloudflare);
|
|
16
16
|
return toRecord(platformCloudflare?.env);
|
|
17
17
|
}
|
|
@@ -10,6 +10,36 @@ export declare function expandIPv6(addr: string): number[] | null;
|
|
|
10
10
|
* blocked outright.
|
|
11
11
|
*/
|
|
12
12
|
export declare function isBlockedUrl(url: string): boolean;
|
|
13
|
+
/** True when a resolved IP address is internal, reserved, or unparseable. */
|
|
14
|
+
export declare function isBlockedAddress(address: string): boolean;
|
|
15
|
+
declare const sameOriginPathBrand: unique symbol;
|
|
16
|
+
/**
|
|
17
|
+
* A path that stays on the current origin when resolved against any base URL.
|
|
18
|
+
* Only `parseSameOriginPath` creates one, so a value of this type is safe to
|
|
19
|
+
* pass to a same-origin fetch or a headless browser navigation.
|
|
20
|
+
*/
|
|
21
|
+
export type SameOriginPath = string & {
|
|
22
|
+
readonly [sameOriginPathBrand]: true;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Parse an untrusted path (for example the `_path` URL option) into a
|
|
26
|
+
* same-origin path. Returns `undefined` for anything that could leave the
|
|
27
|
+
* origin: absolute URLs, other schemes, protocol-relative `//host`,
|
|
28
|
+
* backslash variants, control characters, and paths without a leading `/`
|
|
29
|
+
* (which a string concatenation with the origin turns into `origin@host`).
|
|
30
|
+
*
|
|
31
|
+
* GHSA-h5qf-97cw-2h86: an absolute `_path` navigated the browser renderer to
|
|
32
|
+
* an internal host and reflected the response into the image.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseSameOriginPath(input: unknown): SameOriginPath | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Resolve `path` against `origin` and return the href, but only when the result
|
|
37
|
+
* stays on that origin. Returns null for a missing or unparseable origin, and for
|
|
38
|
+
* anything that resolves elsewhere. The WHATWG parser normalizes leading
|
|
39
|
+
* controls, tabs, and backslashes first, so " //127.0.0.1" resolves cross-origin
|
|
40
|
+
* and is rejected (GHSA-q8hw-4fvp-9rwv).
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveSameOriginUrl(path: string, origin: string | undefined): string | null;
|
|
13
43
|
export interface SafeFetchOptions {
|
|
14
44
|
timeout: number;
|
|
15
45
|
headers?: Record<string, string>;
|
|
@@ -35,3 +65,4 @@ export interface SafeFetchOptions {
|
|
|
35
65
|
* usual missing-asset behaviour.
|
|
36
66
|
*/
|
|
37
67
|
export declare function fetchWithRedirectValidation(initialUrl: string, opts: SafeFetchOptions): Promise<ArrayBuffer | null>;
|
|
68
|
+
export {};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { guardedFetch } from "#og-image/bindings/fetch";
|
|
1
2
|
const RE_IPV6_BRACKETS = /^\[|\]$/g;
|
|
3
|
+
const RE_TRAILING_DOTS = /\.+$/;
|
|
2
4
|
const RE_DIGIT_ONLY = /^\d+$/;
|
|
3
5
|
const RE_INT_IP = /^(?:0x[\da-f]+|\d+)$/i;
|
|
4
6
|
const RE_HEX_GROUP = /^[0-9a-f]{1,4}$/i;
|
|
@@ -18,6 +20,12 @@ function isPrivateIPv4(a, b) {
|
|
|
18
20
|
return true;
|
|
19
21
|
if (a === 0)
|
|
20
22
|
return true;
|
|
23
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
24
|
+
return true;
|
|
25
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
26
|
+
return true;
|
|
27
|
+
if (a >= 224)
|
|
28
|
+
return true;
|
|
21
29
|
return false;
|
|
22
30
|
}
|
|
23
31
|
export function expandIPv6(addr) {
|
|
@@ -88,6 +96,10 @@ function isPrivateIPv6(groups) {
|
|
|
88
96
|
}
|
|
89
97
|
if (g0 === 100 && g1 === 65435 && g2 === 1)
|
|
90
98
|
return true;
|
|
99
|
+
if (g0 === 256 && g1 === 0 && g2 === 0 && g3 === 0)
|
|
100
|
+
return true;
|
|
101
|
+
if (g0 === 8193 && g1 === 0)
|
|
102
|
+
return true;
|
|
91
103
|
if ((g0 & 65024) === 64512)
|
|
92
104
|
return true;
|
|
93
105
|
if ((g0 & 65472) === 65152)
|
|
@@ -114,7 +126,7 @@ export function isBlockedUrl(url) {
|
|
|
114
126
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
115
127
|
return true;
|
|
116
128
|
const hostname = parsed.hostname.toLowerCase();
|
|
117
|
-
const bare = hostname.replace(RE_IPV6_BRACKETS, "");
|
|
129
|
+
const bare = hostname.replace(RE_IPV6_BRACKETS, "").replace(RE_TRAILING_DOTS, "");
|
|
118
130
|
if (bare === "localhost" || bare.endsWith(".localhost"))
|
|
119
131
|
return true;
|
|
120
132
|
const dottedParts = bare.split(".");
|
|
@@ -137,6 +149,50 @@ export function isBlockedUrl(url) {
|
|
|
137
149
|
}
|
|
138
150
|
return false;
|
|
139
151
|
}
|
|
152
|
+
export function isBlockedAddress(address) {
|
|
153
|
+
return isBlockedUrl(`http://${address.includes(":") ? `[${address}]` : address}/`);
|
|
154
|
+
}
|
|
155
|
+
const PROBE_ORIGIN = "http://og-image.invalid";
|
|
156
|
+
function hasUnsafePathChar(input) {
|
|
157
|
+
for (let i = 0; i < input.length; i++) {
|
|
158
|
+
const code = input.charCodeAt(i);
|
|
159
|
+
if (code <= 31 || code === 127 || code === 92)
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
export function parseSameOriginPath(input) {
|
|
165
|
+
if (typeof input !== "string" || input[0] !== "/" || input[1] === "/")
|
|
166
|
+
return void 0;
|
|
167
|
+
if (hasUnsafePathChar(input))
|
|
168
|
+
return void 0;
|
|
169
|
+
let resolved;
|
|
170
|
+
try {
|
|
171
|
+
resolved = new URL(input, PROBE_ORIGIN);
|
|
172
|
+
} catch {
|
|
173
|
+
return void 0;
|
|
174
|
+
}
|
|
175
|
+
if (resolved.origin !== PROBE_ORIGIN)
|
|
176
|
+
return void 0;
|
|
177
|
+
return input;
|
|
178
|
+
}
|
|
179
|
+
export function resolveSameOriginUrl(path, origin) {
|
|
180
|
+
if (!origin)
|
|
181
|
+
return null;
|
|
182
|
+
let base;
|
|
183
|
+
try {
|
|
184
|
+
base = new URL(origin).origin;
|
|
185
|
+
} catch {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
let target;
|
|
189
|
+
try {
|
|
190
|
+
target = new URL(path, base);
|
|
191
|
+
} catch {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
return target.origin === base ? target.href : null;
|
|
195
|
+
}
|
|
140
196
|
export async function fetchWithRedirectValidation(initialUrl, opts) {
|
|
141
197
|
const controller = new AbortController();
|
|
142
198
|
const externalAbort = opts.signal;
|
|
@@ -161,20 +217,23 @@ export async function fetchWithRedirectValidation(initialUrl, opts) {
|
|
|
161
217
|
}
|
|
162
218
|
if (!isTrusted && isBlockedUrl(url))
|
|
163
219
|
return null;
|
|
164
|
-
const res = await
|
|
165
|
-
redirect: "manual",
|
|
220
|
+
const res = await guardedFetch(url, {
|
|
166
221
|
signal: controller.signal,
|
|
167
|
-
headers: opts.headers
|
|
222
|
+
headers: opts.headers,
|
|
223
|
+
isBlockedAddress: isTrusted ? void 0 : isBlockedAddress
|
|
168
224
|
});
|
|
169
225
|
if (res.status >= 300 && res.status < 400) {
|
|
226
|
+
await res.body?.cancel();
|
|
170
227
|
const loc = res.headers.get("location");
|
|
171
228
|
if (!loc)
|
|
172
229
|
return null;
|
|
173
230
|
url = new URL(loc, url).toString();
|
|
174
231
|
continue;
|
|
175
232
|
}
|
|
176
|
-
if (!res.ok)
|
|
233
|
+
if (!res.ok) {
|
|
234
|
+
await res.body?.cancel();
|
|
177
235
|
return null;
|
|
236
|
+
}
|
|
178
237
|
return await res.arrayBuffer();
|
|
179
238
|
}
|
|
180
239
|
return null;
|
|
@@ -4,5 +4,5 @@ export interface GetOgImagePathResult {
|
|
|
4
4
|
path: string;
|
|
5
5
|
hash?: string;
|
|
6
6
|
}
|
|
7
|
-
export declare function getOgImagePath(_pagePath: string, _options?: Partial<OgImageOptionsInternal>): GetOgImagePathResult;
|
|
7
|
+
export declare function getOgImagePath(event: H3Event, _pagePath: string, _options?: Partial<OgImageOptionsInternal>): GetOgImagePathResult;
|
|
8
8
|
export declare function useOgImageRuntimeConfig(e?: H3Event): OgImageRuntimeConfig;
|
|
@@ -3,9 +3,9 @@ import { useRuntimeConfig } from "#nuxtseo/nitro";
|
|
|
3
3
|
import { componentNames } from "#og-image-virtual/component-names.mjs";
|
|
4
4
|
import { buildOgImageUrl } from "../shared.js";
|
|
5
5
|
import { getCloudflareEnv } from "./util/cloudflare.js";
|
|
6
|
-
export function getOgImagePath(_pagePath, _options) {
|
|
7
|
-
const baseURL = useRuntimeConfig().app.baseURL;
|
|
8
|
-
const { defaults, security } = useOgImageRuntimeConfig();
|
|
6
|
+
export function getOgImagePath(event, _pagePath, _options) {
|
|
7
|
+
const baseURL = useRuntimeConfig(event).app.baseURL;
|
|
8
|
+
const { defaults, security } = useOgImageRuntimeConfig(event);
|
|
9
9
|
const extension = _options?.extension || defaults.extension;
|
|
10
10
|
const isStatic = import.meta.prerender && !(security?.secret && security?.strict);
|
|
11
11
|
const options = { ..._options, _path: _pagePath };
|
package/dist/runtime/types.d.ts
CHANGED
|
@@ -4,12 +4,13 @@ import type { SatoriOptions } from 'satori';
|
|
|
4
4
|
import type { JpegOptions, SharpOptions } from 'sharp';
|
|
5
5
|
import type { MaybeRefOrGetter, Ref } from 'vue';
|
|
6
6
|
import type { H3Error, H3Event } from '#nuxtseo/h3';
|
|
7
|
+
import type { SameOriginPath } from './server/util/ssrf.js';
|
|
7
8
|
import type { Timings } from './server/util/timings.js';
|
|
8
9
|
export interface OgImageRenderEventContext {
|
|
9
10
|
e: H3Event;
|
|
10
11
|
extension: 'png' | 'jpeg' | 'jpg' | 'webp' | 'svg' | 'html' | 'json';
|
|
11
12
|
key: string;
|
|
12
|
-
basePath:
|
|
13
|
+
basePath: SameOriginPath;
|
|
13
14
|
renderer: Renderer;
|
|
14
15
|
options: OgImageOptionsInternal;
|
|
15
16
|
isDevToolsContextRequest: boolean;
|
|
@@ -241,6 +242,7 @@ export interface RuntimeCompatibilitySchema {
|
|
|
241
242
|
takumi: 'node' | 'node-dev' | 'wasm' | false;
|
|
242
243
|
sharp: 'node' | false;
|
|
243
244
|
emoji?: 'local' | 'fetch';
|
|
245
|
+
fetch?: 'node' | 'web';
|
|
244
246
|
wasm?: any;
|
|
245
247
|
}
|
|
246
248
|
export interface RuntimeCompatibilityMeta {
|
|
@@ -3,7 +3,7 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, rmSync } from 'node:fs';
|
|
4
4
|
import { readFile, mkdir, writeFile, readdir } from 'node:fs/promises';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
-
import { resolvePath, useNuxt, getNuxtVersion, addTemplate, updateTemplates, directoryToURL, tryResolveModule, addTypeTemplate, defineNuxtModule, createResolver, addImports, hasNuxtModule, hasNuxtModuleCompatibility, getNuxtModuleVersion, addVitePlugin, addBuildPlugin, addServerHandler, addPlugin, addComponentsDir, addServerPlugin } from '@nuxt/kit';
|
|
6
|
+
import { resolvePath, useNuxt, getNuxtVersion, addTemplate, updateTemplates, directoryToURL, tryResolveModule, addTypeTemplate, defineNuxtModule, createResolver, addImports, hasNuxtModule, hasNuxtModuleCompatibility, getNuxtModuleVersion, addVitePlugin, addBuildPlugin, addServerHandler, addServerImports, addPlugin, addComponentsDir, addServerPlugin } from '@nuxt/kit';
|
|
7
7
|
import { defu } from 'defu';
|
|
8
8
|
import { fnv1a64Base36 } from 'fnv1a-64';
|
|
9
9
|
import { installNuxtSiteConfig } from 'nuxt-site-config/kit';
|
|
@@ -119,6 +119,16 @@ async function hasResolvableDependency(dep) {
|
|
|
119
119
|
return null;
|
|
120
120
|
}).then((r) => r && r !== dep);
|
|
121
121
|
}
|
|
122
|
+
const MIN_SATORI_VERSION = "0.33.5";
|
|
123
|
+
function isSupportedSatoriVersion(version) {
|
|
124
|
+
const current = (version || "").split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
125
|
+
const minimum = MIN_SATORI_VERSION.split(".").map(Number);
|
|
126
|
+
for (let i = 0; i < minimum.length; i++) {
|
|
127
|
+
if ((current[i] ?? 0) !== minimum[i])
|
|
128
|
+
return (current[i] ?? 0) > minimum[i];
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
122
132
|
const VALID_RENDERER_SUFFIXES = ["satori", "browser", "takumi"];
|
|
123
133
|
const RE_RENDERER_SUFFIX_CI = /\.?(satori|browser|takumi)$/i;
|
|
124
134
|
const RE_RENDERER_SUFFIX = /(Satori|Browser|Takumi)$/;
|
|
@@ -192,6 +202,8 @@ const cloudflare = {
|
|
|
192
202
|
sharp: false,
|
|
193
203
|
emoji: "fetch",
|
|
194
204
|
// edge size limits - use API instead of bundling 24MB icons
|
|
205
|
+
fetch: "web",
|
|
206
|
+
// no DNS API
|
|
195
207
|
wasm: {
|
|
196
208
|
esmImport: true
|
|
197
209
|
// Do NOT set lazy here — Nitro's cloudflare preset uses lazy: false because
|
|
@@ -214,8 +226,9 @@ const WebContainer = {
|
|
|
214
226
|
satori: "wasm-fs",
|
|
215
227
|
takumi: "wasm",
|
|
216
228
|
sharp: false,
|
|
217
|
-
emoji: "fetch"
|
|
229
|
+
emoji: "fetch",
|
|
218
230
|
// webcontainer has size constraints
|
|
231
|
+
fetch: "web"
|
|
219
232
|
};
|
|
220
233
|
const RuntimeCompatibility = {
|
|
221
234
|
"nitro-dev": NodeDevRuntime,
|
|
@@ -256,6 +269,7 @@ const RuntimeCompatibility = {
|
|
|
256
269
|
sharp: false,
|
|
257
270
|
emoji: "fetch",
|
|
258
271
|
// edge size limits
|
|
272
|
+
fetch: "web",
|
|
259
273
|
wasm: {
|
|
260
274
|
rollup: {
|
|
261
275
|
targetEnv: "auto-inline",
|
|
@@ -276,6 +290,7 @@ const RuntimeCompatibility = {
|
|
|
276
290
|
sharp: false,
|
|
277
291
|
emoji: "fetch",
|
|
278
292
|
// edge size limits - bundling 24MB icons not viable
|
|
293
|
+
fetch: "web",
|
|
279
294
|
wasm: {
|
|
280
295
|
// lowers workers kb size
|
|
281
296
|
esmImport: true,
|
|
@@ -351,32 +366,32 @@ async function applyNitroPresetCompatibility(nitroConfig, options) {
|
|
|
351
366
|
);
|
|
352
367
|
if (resolvedCompatibility.satori) {
|
|
353
368
|
const satoriPackage = await readPackageJSON(`${satoriPkgDir}/package.json`);
|
|
354
|
-
if (satoriPackage.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
const base64 = (await readFile(harfbuzzWasmPath)).toString("base64");
|
|
368
|
-
nitroConfig.virtual["#og-image/harfbuzz-wasm"] = `export default imports => WebAssembly.instantiate(Uint8Array.from(atob(${JSON.stringify(base64)}), c => c.charCodeAt(0)), imports)`;
|
|
369
|
-
}
|
|
370
|
-
} else if (Number.parseInt(getNuxtVersion(useNuxt()), 10) >= 5) {
|
|
371
|
-
const config = nitroConfig;
|
|
372
|
-
config.traceDeps = [.../* @__PURE__ */ new Set([...config.traceDeps || [], "harfbuzzjs*"])];
|
|
369
|
+
if (!isSupportedSatoriVersion(satoriPackage.version))
|
|
370
|
+
throw new Error(`[Nuxt OG Image] Satori ${satoriPackage.version} is not supported. Satori ${MIN_SATORI_VERSION} or later is required (GHSA-wx4j-mvgx-mqwp). Run: npm install satori@latest`);
|
|
371
|
+
const harfbuzzWasmPath = resolveModulePath("harfbuzzjs/hb.wasm", { from: `${satoriPkgDir}/package.json` });
|
|
372
|
+
if (resolvedCompatibility.satori === "wasm" || target.replace(RE_LEGACY_SUFFIX, "") === "netlify-edge") {
|
|
373
|
+
const harfbuzzDir = harfbuzzWasmPath.slice(0, -"hb.wasm".length);
|
|
374
|
+
nitroConfig.virtual["#og-image/harfbuzz-callbacks"] = resolvedCompatibility.satori === "wasm" ? await prepareHarfBuzzCallbacks(`${harfbuzzDir}hbjs.js`, join(useNuxt().options.buildDir, "cache/og-image/harfbuzz")) : "export default undefined";
|
|
375
|
+
Object.assign(nitroConfig.alias, {
|
|
376
|
+
"harfbuzzjs": await resolve.resolvePath("./runtime/server/og-image/bindings/satori/harfbuzz"),
|
|
377
|
+
"#og-image/harfbuzz-factory": `${harfbuzzDir}hb.js`,
|
|
378
|
+
"#og-image/harfbuzz-adapter": `${harfbuzzDir}hbjs.js`
|
|
379
|
+
});
|
|
380
|
+
if (resolvedCompatibility.satori === "wasm") {
|
|
381
|
+
nitroConfig.alias["#og-image/harfbuzz-wasm"] = `${harfbuzzWasmPath}?module`;
|
|
373
382
|
} else {
|
|
374
|
-
|
|
375
|
-
nitroConfig.
|
|
376
|
-
...nitroConfig.externals.traceInclude || [],
|
|
377
|
-
harfbuzzWasmPath
|
|
378
|
-
])];
|
|
383
|
+
const base64 = (await readFile(harfbuzzWasmPath)).toString("base64");
|
|
384
|
+
nitroConfig.virtual["#og-image/harfbuzz-wasm"] = `export default imports => WebAssembly.instantiate(Uint8Array.from(atob(${JSON.stringify(base64)}), c => c.charCodeAt(0)), imports)`;
|
|
379
385
|
}
|
|
386
|
+
} else if (Number.parseInt(getNuxtVersion(useNuxt()), 10) >= 5) {
|
|
387
|
+
const config = nitroConfig;
|
|
388
|
+
config.traceDeps = [.../* @__PURE__ */ new Set([...config.traceDeps || [], "harfbuzzjs*"])];
|
|
389
|
+
} else {
|
|
390
|
+
nitroConfig.externals = nitroConfig.externals || {};
|
|
391
|
+
nitroConfig.externals.traceInclude = [.../* @__PURE__ */ new Set([
|
|
392
|
+
...nitroConfig.externals.traceInclude || [],
|
|
393
|
+
harfbuzzWasmPath
|
|
394
|
+
])];
|
|
380
395
|
}
|
|
381
396
|
}
|
|
382
397
|
if (Object.values(resolvedCompatibility).includes("wasm")) {
|
|
@@ -4928,6 +4943,10 @@ declare module '#og-image/renderers/takumi' {
|
|
|
4928
4943
|
export default _default
|
|
4929
4944
|
}
|
|
4930
4945
|
|
|
4946
|
+
declare module '#og-image/bindings/fetch' {
|
|
4947
|
+
export { guardedFetch } from '${typesPath.replace("/types", "")}/server/og-image/bindings/fetch/web'
|
|
4948
|
+
}
|
|
4949
|
+
|
|
4931
4950
|
declare module '#og-image/emoji-transform' {
|
|
4932
4951
|
import type { OgImageRenderEventContext } from '${typesPath}'
|
|
4933
4952
|
export function getEmojiSvg(ctx: OgImageRenderEventContext, emoji: string): Promise<string | null>
|
|
@@ -5172,6 +5191,7 @@ Or pin to a specific version in package.json:
|
|
|
5172
5191
|
setRuntimeAlias("#og-image-cache", resolve("./runtime/server/og-image/cache/lru"));
|
|
5173
5192
|
const preset = resolveOgImagePreset(nuxt.options.nitro);
|
|
5174
5193
|
const targetCompatibility = getPresetNitroPresetCompatibility(preset);
|
|
5194
|
+
setRuntimeAlias("#og-image/bindings/fetch", resolve(`./runtime/server/og-image/bindings/fetch/${targetCompatibility.fetch ?? "node"}`));
|
|
5175
5195
|
const runtimeCompatibilityMeta = {};
|
|
5176
5196
|
const normalizedPreset = preset.replace(RE_LEGACY_SUFFIX, "");
|
|
5177
5197
|
const isCloudflareWorkers = ["cloudflare", "cloudflare-module", "cloudflare-durable"].includes(normalizedPreset);
|
|
@@ -5587,6 +5607,9 @@ ${familyInfo}`);
|
|
|
5587
5607
|
name: "getOgImagePath",
|
|
5588
5608
|
from: resolve("./runtime/app/utils")
|
|
5589
5609
|
});
|
|
5610
|
+
addServerImports([
|
|
5611
|
+
{ name: "getOgImageUrl", from: resolve("./runtime/server/nitro") }
|
|
5612
|
+
]);
|
|
5590
5613
|
const basePluginPath = `./runtime/app/plugins${config.zeroRuntime ? "/__zero-runtime" : ""}`;
|
|
5591
5614
|
addPlugin({ mode: "server", src: resolve(`${basePluginPath}/route-rule-og-image.server`) });
|
|
5592
5615
|
addPlugin({ mode: "server", src: resolve(`${basePluginPath}/og-image-canonical-urls.server`) });
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-og-image",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "6.
|
|
4
|
+
"version": "6.9.0",
|
|
5
5
|
"description": "Enlightened OG Image generation for Nuxt.",
|
|
6
6
|
"author": {
|
|
7
7
|
"website": "https://harlanzw.com",
|
|
@@ -53,13 +53,13 @@
|
|
|
53
53
|
"@takumi-rs/core": "^1.0.0-beta.3 || ^2.0.0",
|
|
54
54
|
"@takumi-rs/wasm": "^1.0.0-beta.3 || ^2.0.0",
|
|
55
55
|
"@unhead/vue": "^2.0.5 || ^3.0.0",
|
|
56
|
-
"@unocss/config": "^66.10.
|
|
57
|
-
"@unocss/core": "^66.10.
|
|
58
|
-
"fontless": "^0.2.0 || ^0.4.3",
|
|
56
|
+
"@unocss/config": "^66.10.5",
|
|
57
|
+
"@unocss/core": "^66.10.5",
|
|
58
|
+
"fontless": "^0.2.0 || ^0.4.3 || ^1.0.0",
|
|
59
59
|
"nitropack": "^2.13.4",
|
|
60
60
|
"playwright-core": "^1.50.0",
|
|
61
|
-
"satori": ">=0.
|
|
62
|
-
"sharp": "^0.
|
|
61
|
+
"satori": ">=0.33.5",
|
|
62
|
+
"sharp": "^0.35.4",
|
|
63
63
|
"tailwindcss": "^4.0.0",
|
|
64
64
|
"unifont": "^0.7.0 || ^0.8.3",
|
|
65
65
|
"unstorage": "^1.15.0",
|
|
@@ -110,9 +110,9 @@
|
|
|
110
110
|
}
|
|
111
111
|
},
|
|
112
112
|
"dependencies": {
|
|
113
|
-
"@clack/prompts": "^1.8.
|
|
113
|
+
"@clack/prompts": "^1.8.1",
|
|
114
114
|
"@nuxt/kit": "^4.5.2",
|
|
115
|
-
"@vue/compiler-sfc": "^3.5.
|
|
115
|
+
"@vue/compiler-sfc": "^3.5.43",
|
|
116
116
|
"chrome-launcher": "^1.2.1",
|
|
117
117
|
"consola": "^3.4.2",
|
|
118
118
|
"defu": "^6.1.7",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"exsolve": "^1.1.1",
|
|
121
121
|
"fnv1a-64": "^0.1.2",
|
|
122
122
|
"lightningcss": "^1.33.0",
|
|
123
|
-
"magic-string": "^1.
|
|
123
|
+
"magic-string": "^1.4.1",
|
|
124
124
|
"magicast": "^0.5.5",
|
|
125
125
|
"mocked-exports": "^0.1.1",
|
|
126
126
|
"nuxt-site-config": "^4.2.3",
|
|
@@ -129,7 +129,7 @@
|
|
|
129
129
|
"object-identity": "^0.2.3",
|
|
130
130
|
"ofetch": "^1.5.1",
|
|
131
131
|
"ohash": "^2.0.12",
|
|
132
|
-
"oxc-parser": "^0.
|
|
132
|
+
"oxc-parser": "^0.151.0",
|
|
133
133
|
"oxc-walker": "^1.1.1",
|
|
134
134
|
"pathe": "^2.0.3",
|
|
135
135
|
"pkg-types": "^2.3.3",
|
|
@@ -139,21 +139,21 @@
|
|
|
139
139
|
"tinyexec": "^1.3.1",
|
|
140
140
|
"ufo": "^1.6.4",
|
|
141
141
|
"ultrahtml": "^1.7.0",
|
|
142
|
-
"unplugin": "^3.
|
|
142
|
+
"unplugin": "^3.4.0"
|
|
143
143
|
},
|
|
144
144
|
"devDependencies": {
|
|
145
145
|
"@antfu/eslint-config": "^9.5.1",
|
|
146
146
|
"@fontsource/noto-sans-sc": "^5.3.0",
|
|
147
147
|
"@iconify-json/carbon": "^1.2.27",
|
|
148
148
|
"@iconify-json/logos": "^1.2.14",
|
|
149
|
-
"@iconify-json/noto": "^1.2.
|
|
149
|
+
"@iconify-json/noto": "^1.2.8",
|
|
150
150
|
"@iconify-json/ri": "^1.2.10",
|
|
151
|
-
"@iconify-json/tabler": "^1.2.
|
|
151
|
+
"@iconify-json/tabler": "^1.2.39",
|
|
152
152
|
"@iconify/types": "^2.0.0",
|
|
153
153
|
"@img/sharp-linux-x64": "^0.35.4",
|
|
154
|
-
"@nuxt/content": "^3.16.
|
|
155
|
-
"@nuxt/devtools": "4.0.0-
|
|
156
|
-
"@nuxt/devtools-kit": "4.0.0-
|
|
154
|
+
"@nuxt/content": "^3.16.1",
|
|
155
|
+
"@nuxt/devtools": "4.0.0-beta.1",
|
|
156
|
+
"@nuxt/devtools-kit": "4.0.0-beta.1",
|
|
157
157
|
"@nuxt/fonts": "^0.14.0",
|
|
158
158
|
"@nuxt/icon": "^2.5.1",
|
|
159
159
|
"@nuxt/image": "^2.1.0",
|
|
@@ -167,30 +167,30 @@
|
|
|
167
167
|
"@resvg/resvg-js": "^2.6.2",
|
|
168
168
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
169
169
|
"@tailwindcss/vite": "^4.3.3",
|
|
170
|
-
"@takumi-rs/core": "^2.
|
|
170
|
+
"@takumi-rs/core": "^2.14.0",
|
|
171
171
|
"@takumi-rs/core-v1": "npm:@takumi-rs/core@1.8.7",
|
|
172
|
-
"@takumi-rs/helpers": "^2.
|
|
172
|
+
"@takumi-rs/helpers": "^2.14.0",
|
|
173
173
|
"@takumi-rs/helpers-v1": "npm:@takumi-rs/helpers@1.8.7",
|
|
174
|
-
"@takumi-rs/wasm": "^2.
|
|
174
|
+
"@takumi-rs/wasm": "^2.14.0",
|
|
175
175
|
"@takumi-rs/wasm-v1": "npm:@takumi-rs/wasm@1.8.7",
|
|
176
|
-
"@unocss/config": "^66.10.
|
|
177
|
-
"@unocss/core": "^66.10.
|
|
178
|
-
"@unocss/nuxt": "^66.10.
|
|
179
|
-
"@unocss/preset-icons": "^66.10.
|
|
180
|
-
"@unocss/preset-wind": "^66.10.
|
|
181
|
-
"@unocss/preset-wind4": "^66.10.
|
|
182
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
183
|
-
"@vue/compiler-core": "^3.5.
|
|
184
|
-
"@vueuse/nuxt": "^
|
|
176
|
+
"@unocss/config": "^66.10.5",
|
|
177
|
+
"@unocss/core": "^66.10.5",
|
|
178
|
+
"@unocss/nuxt": "^66.10.5",
|
|
179
|
+
"@unocss/preset-icons": "^66.10.5",
|
|
180
|
+
"@unocss/preset-wind": "^66.10.5",
|
|
181
|
+
"@unocss/preset-wind4": "^66.10.5",
|
|
182
|
+
"@vitejs/plugin-vue": "^6.0.9",
|
|
183
|
+
"@vue/compiler-core": "^3.5.43",
|
|
184
|
+
"@vueuse/nuxt": "^15.0.0",
|
|
185
185
|
"birpc": "^4.2.0",
|
|
186
186
|
"bumpp": "^12.3.0",
|
|
187
|
-
"eslint": "^10.
|
|
188
|
-
"eslint-plugin-harlanzw": "^0.
|
|
189
|
-
"fontless": "^0.
|
|
187
|
+
"eslint": "^10.11.0",
|
|
188
|
+
"eslint-plugin-harlanzw": "^0.22.0",
|
|
189
|
+
"fontless": "^1.0.0",
|
|
190
190
|
"fontless-v0-2": "npm:fontless@0.2.0",
|
|
191
191
|
"globby": "^16.2.4",
|
|
192
192
|
"happy-dom": "^20.14.5",
|
|
193
|
-
"hookable": "^6.1.
|
|
193
|
+
"hookable": "^6.1.2",
|
|
194
194
|
"jest-image-snapshot": "^6.5.2",
|
|
195
195
|
"lightningcss": "^1.33.0",
|
|
196
196
|
"nitropack": "^2.13.4",
|
|
@@ -199,24 +199,21 @@
|
|
|
199
199
|
"playwright": "^1.63.0",
|
|
200
200
|
"playwright-core": "^1.63.0",
|
|
201
201
|
"sass": "^1.104.1",
|
|
202
|
-
"satori": "^0.33.
|
|
203
|
-
"satori-v0-19": "npm:satori@0.19.2",
|
|
204
|
-
"satori-v0-32": "npm:satori@0.32.0",
|
|
202
|
+
"satori": "^0.33.5",
|
|
205
203
|
"sharp": "^0.35.4",
|
|
206
|
-
"sharp-v0-34": "npm:sharp@0.34.0",
|
|
207
204
|
"sirv": "^3.0.2",
|
|
208
205
|
"tailwindcss": "^4.3.3",
|
|
209
206
|
"typescript": "6.0.3",
|
|
210
207
|
"unbuild": "^3.6.1",
|
|
211
208
|
"unifont": "^0.8.3",
|
|
212
209
|
"unifont-v0-7": "npm:unifont@0.7.0",
|
|
213
|
-
"unocss": "^66.10.
|
|
214
|
-
"vitest": "^5.0.
|
|
215
|
-
"vue": "^3.5.
|
|
210
|
+
"unocss": "^66.10.5",
|
|
211
|
+
"vitest": "^5.0.1",
|
|
212
|
+
"vue": "^3.5.43",
|
|
216
213
|
"vue-tsc": "^3.3.11",
|
|
217
|
-
"wrangler": "^4.
|
|
214
|
+
"wrangler": "^4.136.1",
|
|
218
215
|
"yoga-wasm-web": "^0.3.3",
|
|
219
|
-
"zod": "^4.6.
|
|
216
|
+
"zod": "^4.6.5"
|
|
220
217
|
},
|
|
221
218
|
"scripts": {
|
|
222
219
|
"bench:edge-startup": "nuxt build bench/fixtures/edge-startup && node bench/edge-startup.mjs bench/fixtures/edge-startup/.netlify/edge-functions/server/server.js",
|
package/types/virtual.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ declare module '#og-image/renderers/browser' {
|
|
|
29
29
|
export default renderer
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
declare module '#og-image/bindings/fetch' {
|
|
33
|
+
export function guardedFetch(url: string, init: {
|
|
34
|
+
headers?: Record<string, string>
|
|
35
|
+
signal: AbortSignal
|
|
36
|
+
isBlockedAddress?: (address: string) => boolean
|
|
37
|
+
}): Promise<Response>
|
|
38
|
+
}
|
|
39
|
+
|
|
32
40
|
declare module '#og-image/bindings/satori' {
|
|
33
41
|
const satori: typeof import('satori').satori
|
|
34
42
|
export default satori
|