nuxt-og-image 6.10.4 → 7.0.1
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 +10 -14
- package/dist/chunks/tw4.mjs +6 -6
- package/dist/chunks/uno.mjs +7 -7
- package/dist/cli.mjs +4 -4
- package/dist/devtools/components/og-image/RendererSelectModal.vue +1 -1
- package/dist/devtools/lib/og-image/fonts.ts +38 -0
- package/dist/devtools/lib/og-image/types.ts +7 -0
- package/dist/devtools/pages/og-image/index.vue +19 -14
- package/dist/module.d.mts +14 -10
- package/dist/module.d.ts +14 -10
- package/dist/module.json +2 -2
- package/dist/module.mjs +6 -6
- package/dist/runtime/app/client-utils.js +7 -4
- package/dist/runtime/app/components/Templates/Community/SaaS.satori.vue +1 -1
- package/dist/runtime/app/components/Templates/Community/SimpleBlog.satori.vue +1 -1
- package/dist/runtime/app/disabled/components.d.ts +1 -0
- package/dist/runtime/app/disabled/components.js +0 -0
- package/dist/runtime/app/disabled/composables/defineOgImage.d.ts +1 -0
- package/dist/runtime/app/disabled/composables/defineOgImage.js +1 -0
- package/dist/runtime/app/disabled/composables/defineOgImageComponent.d.ts +1 -0
- package/dist/runtime/app/disabled/composables/defineOgImageComponent.js +1 -0
- package/dist/runtime/app/disabled/composables/defineOgImageScreenshot.d.ts +1 -0
- package/dist/runtime/app/disabled/composables/defineOgImageScreenshot.js +1 -0
- package/dist/runtime/app/disabled/composables/mock.d.ts +1 -0
- package/dist/runtime/app/disabled/composables/mock.js +1 -0
- package/dist/runtime/app/disabled/index.d.ts +3 -0
- package/dist/runtime/app/disabled/index.js +3 -0
- package/dist/runtime/app/index.d.ts +4 -0
- package/dist/runtime/app/index.js +4 -0
- package/dist/runtime/app/utils/plugins.js +1 -1
- package/dist/runtime/app/utils.js +2 -2
- package/dist/runtime/server/index.d.ts +1 -0
- package/dist/runtime/server/index.js +1 -0
- package/dist/runtime/server/middleware/signing.d.ts +2 -0
- package/dist/runtime/server/middleware/signing.js +19 -0
- package/dist/runtime/server/nitro.d.ts +3 -3
- package/dist/runtime/server/nitro.js +4 -4
- package/dist/runtime/server/og-image/bindings/font-assets/dev-prerender.js +8 -49
- package/dist/runtime/server/og-image/bindings/font-assets/node.js +3 -2
- package/dist/runtime/server/og-image/bindings/satori/node.d.ts +1 -2
- package/dist/runtime/server/og-image/bindings/satori/wasm-fs.d.ts +1 -2
- package/dist/runtime/server/og-image/bindings/satori/wasm-fs.js +1 -1
- package/dist/runtime/server/og-image/bindings/satori/wasm.d.ts +1 -2
- package/dist/runtime/server/og-image/bindings/satori/wasm.js +1 -1
- package/dist/runtime/server/og-image/browser/screenshot.js +5 -3
- package/dist/runtime/server/og-image/cache/publish.d.ts +46 -0
- package/dist/runtime/server/og-image/cache/publish.js +91 -0
- package/dist/runtime/server/og-image/cache/published.d.ts +17 -0
- package/dist/runtime/server/og-image/cache/published.js +49 -0
- package/dist/runtime/server/og-image/context.js +1 -1
- package/dist/runtime/server/og-image/core/plugins/imageSrc.js +2 -5
- package/dist/runtime/server/og-image/core/transforms/emojis/index.d.ts +3 -2
- package/dist/runtime/server/og-image/core/vnodes.js +19 -1
- package/dist/runtime/server/og-image/font-source.d.ts +1 -0
- package/dist/runtime/server/og-image/font-source.js +1 -0
- package/dist/runtime/server/og-image/fonts.d.ts +1 -1
- package/dist/runtime/server/og-image/fonts.js +1 -1
- package/dist/runtime/server/og-image/satori/filters.d.ts +1 -0
- package/dist/runtime/server/og-image/satori/filters.js +14 -0
- package/dist/runtime/server/og-image/satori/instances.d.ts +1 -2
- package/dist/runtime/server/og-image/satori/plugins/classes.js +0 -7
- package/dist/runtime/server/og-image/satori/renderer.js +2 -1
- package/dist/runtime/server/og-image/satori/vnodes.js +1 -4
- package/dist/runtime/server/og-image/takumi/renderer.js +11 -47
- package/dist/runtime/server/og-image/utils/css.d.ts +0 -5
- package/dist/runtime/server/og-image/utils/css.js +0 -5
- package/dist/runtime/server/plugins/auto-eject-nitro3.d.ts +2 -0
- package/dist/runtime/server/plugins/auto-eject-nitro3.js +9 -0
- package/dist/runtime/server/plugins/auto-eject.js +3 -31
- package/dist/runtime/server/plugins/prerender.js +67 -2
- package/dist/runtime/server/plugins/publish.d.ts +2 -0
- package/dist/runtime/server/plugins/publish.js +44 -0
- package/dist/runtime/server/routes/debug.json.d.ts +10 -1
- package/dist/runtime/server/routes/debug.json.js +4 -4
- package/dist/runtime/server/routes/static-font.d.ts +2 -0
- package/dist/runtime/server/routes/static-font.js +22 -0
- package/dist/runtime/server/signing-secret.d.ts +8 -0
- package/dist/runtime/server/signing-secret.js +75 -0
- package/dist/runtime/server/util/auto-eject-lifecycle.d.ts +13 -0
- package/dist/runtime/server/util/auto-eject-lifecycle.js +31 -0
- package/dist/runtime/server/util/cache.d.ts +1 -0
- package/dist/runtime/server/util/cache.js +12 -6
- package/dist/runtime/server/util/cloudflare.d.ts +2 -2
- package/dist/runtime/server/util/eventHandlers.js +68 -3
- package/dist/runtime/server/util/fetchLocalAsset.js +2 -2
- package/dist/runtime/server/util/origin.d.ts +3 -0
- package/dist/runtime/server/util/origin.js +4 -0
- package/dist/runtime/server/util/publishWarnings.d.ts +6 -0
- package/dist/runtime/server/util/publishWarnings.js +6 -0
- package/dist/runtime/server/utils.d.ts +3 -3
- package/dist/runtime/server/utils.js +5 -10
- package/dist/runtime/shared/publish.d.ts +13 -0
- package/dist/runtime/shared/publish.js +17 -0
- package/dist/runtime/shared/urlEncoding.js +9 -4
- package/dist/runtime/types.d.ts +11 -16
- package/dist/shared/{nuxt-og-image.Bcegsloy.mjs → nuxt-og-image.DR_SxdDv.mjs} +1224 -1109
- package/package.json +60 -62
- package/skills/nuxt-og-image/SKILL.md +31 -14
- package/types/virtual.d.ts +8 -0
- package/dist/runtime/server/og-image/satori/plugins/emojis.d.ts +0 -2
- package/dist/runtime/server/og-image/satori/plugins/emojis.js +0 -112
- package/dist/runtime/server/og-image/satori/plugins/flex.d.ts +0 -2
- package/dist/runtime/server/og-image/satori/plugins/flex.js +0 -48
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defu } from "defu";
|
|
2
2
|
import { withBase } from "ufo";
|
|
3
|
-
import compatibility from "#og-image/compatibility";
|
|
4
3
|
import { logger } from "../../../logger.js";
|
|
5
4
|
import { fetchLocalAsset } from "../../util/fetchLocalAsset.js";
|
|
6
5
|
import { getFetchTimeout } from "../../util/fetchTimeout.js";
|
|
7
6
|
import { fetchWithRedirectValidation, isBlockedUrl } from "../../util/ssrf.js";
|
|
8
7
|
import { withTimeout } from "../../util/withTimeout.js";
|
|
8
|
+
import { TAKUMI_FORMATS } from "../font-source.js";
|
|
9
9
|
import { buildSubsetFamilyChain, extractCodepoints, getDefaultFontFamily, loadFontsForRenderer, resolveSubsetChain } from "../fonts.js";
|
|
10
10
|
import { getExtractResourceUrls, getTakumi } from "./instances.js";
|
|
11
11
|
import { createTakumiNodes } from "./nodes.js";
|
|
@@ -17,8 +17,6 @@ async function getTakumiState(event) {
|
|
|
17
17
|
const Renderer = await getTakumi();
|
|
18
18
|
nitro._takumiState = {
|
|
19
19
|
renderer: new Renderer(),
|
|
20
|
-
loadedFontKeys: /* @__PURE__ */ new Set(),
|
|
21
|
-
loadedFamilies: /* @__PURE__ */ new Set(),
|
|
22
20
|
lock: Promise.resolve()
|
|
23
21
|
};
|
|
24
22
|
return nitro._takumiState;
|
|
@@ -40,8 +38,6 @@ function withTakumiLock(state, timeoutMs, fn, onLockTimeout) {
|
|
|
40
38
|
try {
|
|
41
39
|
const Renderer = await getTakumi();
|
|
42
40
|
state.renderer = new Renderer();
|
|
43
|
-
state.loadedFontKeys.clear();
|
|
44
|
-
state.loadedFamilies.clear();
|
|
45
41
|
} catch (resetErr) {
|
|
46
42
|
logger.warn(`failed to reset takumi renderer after lock timeout: ${resetErr?.message || resetErr}`);
|
|
47
43
|
}
|
|
@@ -106,39 +102,15 @@ function toUint8Array(data) {
|
|
|
106
102
|
return new Uint8Array(data);
|
|
107
103
|
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
108
104
|
}
|
|
109
|
-
function createFontDescriptor(entry
|
|
105
|
+
function createFontDescriptor(entry) {
|
|
110
106
|
return {
|
|
111
|
-
|
|
107
|
+
key: entry.binaryKey,
|
|
112
108
|
name: entry.family,
|
|
113
109
|
data: toUint8Array(entry.data),
|
|
114
110
|
...entry.weight !== void 0 ? { weight: entry.weight } : {},
|
|
115
111
|
...entry.style ? { style: entry.style } : {}
|
|
116
112
|
};
|
|
117
113
|
}
|
|
118
|
-
function addLoadedFamilies(state, entry, registeredFamilies) {
|
|
119
|
-
if (Array.isArray(registeredFamilies) && registeredFamilies.length) {
|
|
120
|
-
for (const family of registeredFamilies) {
|
|
121
|
-
const name = family?.name;
|
|
122
|
-
if (typeof name === "string")
|
|
123
|
-
state.loadedFamilies.add(name);
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
126
|
-
state.loadedFamilies.add(entry.family);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
async function loadFontsIntoRenderer(state, entries) {
|
|
130
|
-
for (const entry of entries) {
|
|
131
|
-
if (state.loadedFontKeys.has(entry.binaryKey))
|
|
132
|
-
continue;
|
|
133
|
-
try {
|
|
134
|
-
const registeredFamilies = await state.renderer.loadFont(createFontDescriptor(entry));
|
|
135
|
-
addLoadedFamilies(state, entry, registeredFamilies);
|
|
136
|
-
} catch (err) {
|
|
137
|
-
logger.warn(`Failed to load font "${entry.family}" with takumi renderer: ${err.message}`);
|
|
138
|
-
}
|
|
139
|
-
state.loadedFontKeys.add(entry.binaryKey);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
114
|
function normalizeImageSources(images) {
|
|
143
115
|
return (images || []).map((image) => ({
|
|
144
116
|
src: image.src,
|
|
@@ -183,11 +155,10 @@ function rewriteFontFamilies(node, loadedFamilies, subsetChains) {
|
|
|
183
155
|
}
|
|
184
156
|
async function createImage(event, format) {
|
|
185
157
|
const { options, timings } = event;
|
|
186
|
-
const isTakumiV2 = compatibility.takumiVersion === 2;
|
|
187
158
|
const { fontFamilyOverride, defaultFont } = getDefaultFontFamily(options);
|
|
188
159
|
const nodes = await createTakumiNodes(event);
|
|
189
160
|
const codepoints = extractCodepoints(nodes);
|
|
190
|
-
const fonts = await timings.measure("font-load", () => loadFontsForRenderer(event, { supportedFormats:
|
|
161
|
+
const fonts = await timings.measure("font-load", () => loadFontsForRenderer(event, { supportedFormats: TAKUMI_FORMATS, component: options.component, fontFamilyOverride: fontFamilyOverride || defaultFont, codepoints }));
|
|
191
162
|
const fontEntries = dedupeFontsByBinary(fonts);
|
|
192
163
|
const hookTimeout = event.runtimeConfig.security?.renderTimeout ?? 15e3;
|
|
193
164
|
await withTimeout(
|
|
@@ -200,11 +171,9 @@ async function createImage(event, format) {
|
|
|
200
171
|
const extractResourceUrls = await timings.measure("takumi-extract-init", () => getExtractResourceUrls());
|
|
201
172
|
const resourceUrls = await extractResourceUrls(nodes);
|
|
202
173
|
const baseURL = event.runtimeConfig.app.baseURL;
|
|
203
|
-
const { images: optionImages,
|
|
174
|
+
const { images: optionImages, ...takumiOptions } = options.takumi || {};
|
|
204
175
|
const images = [
|
|
205
|
-
...normalizeImageSources(optionImages)
|
|
206
|
-
...normalizeImageSources(optionFetchedResources),
|
|
207
|
-
...normalizeImageSources(persistentImages)
|
|
176
|
+
...normalizeImageSources(optionImages)
|
|
208
177
|
];
|
|
209
178
|
if (resourceUrls.length) {
|
|
210
179
|
const fetchTimeout = getFetchTimeout(event.runtimeConfig);
|
|
@@ -245,7 +214,7 @@ async function createImage(event, format) {
|
|
|
245
214
|
width: Math.min(Number(options.width) * dpr, maxDim),
|
|
246
215
|
height: Math.min(Number(options.height) * dpr, maxDim),
|
|
247
216
|
format,
|
|
248
|
-
|
|
217
|
+
images,
|
|
249
218
|
devicePixelRatio: dpr
|
|
250
219
|
});
|
|
251
220
|
const lockTimeout = event.runtimeConfig.security?.renderTimeout ?? 15e3;
|
|
@@ -253,14 +222,9 @@ async function createImage(event, format) {
|
|
|
253
222
|
return await withTakumiLock(state, lockTimeout, () => {
|
|
254
223
|
endLockWait();
|
|
255
224
|
return timings.measure("render-takumi", async () => {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
renderOptions.fonts = fontEntries.map((entry) => createFontDescriptor(entry, true));
|
|
260
|
-
renderOptions.fontFamilies = [...loadedFamilies];
|
|
261
|
-
} else {
|
|
262
|
-
await loadFontsIntoRenderer(state, fontEntries);
|
|
263
|
-
}
|
|
225
|
+
const loadedFamilies = new Set(fontEntries.map((entry) => entry.family));
|
|
226
|
+
renderOptions.fonts = fontEntries.map((entry) => createFontDescriptor(entry));
|
|
227
|
+
renderOptions.fontFamilies = [...loadedFamilies];
|
|
264
228
|
const rootStyle = nodes.style ?? {};
|
|
265
229
|
if (fontFamilyOverride) {
|
|
266
230
|
const chain = subsetChains.get(fontFamilyOverride);
|
|
@@ -293,7 +257,7 @@ const TakumiRenderer = {
|
|
|
293
257
|
async debug(e) {
|
|
294
258
|
const [vnodes, fonts] = await Promise.all([
|
|
295
259
|
createTakumiNodes(e),
|
|
296
|
-
loadFontsForRenderer(e, { supportedFormats:
|
|
260
|
+
loadFontsForRenderer(e, { supportedFormats: TAKUMI_FORMATS, component: e.options.component })
|
|
297
261
|
]);
|
|
298
262
|
return {
|
|
299
263
|
vnodes,
|
|
@@ -6,9 +6,4 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function splitCssDeclarations(style: string): string[];
|
|
8
8
|
export declare const GRADIENT_COLOR_SPACE_RE: RegExp;
|
|
9
|
-
/**
|
|
10
|
-
* Strip gradient color interpolation methods (e.g. `in oklab`, `in oklch`)
|
|
11
|
-
* from CSS gradient values. Image renderers (Satori, Takumi) don't support them.
|
|
12
|
-
*/
|
|
13
|
-
export declare function stripGradientColorSpace(value: string): string;
|
|
14
9
|
export declare function resolveColorMix(value: string): string;
|
|
@@ -29,11 +29,6 @@ export function splitCssDeclarations(style) {
|
|
|
29
29
|
return declarations;
|
|
30
30
|
}
|
|
31
31
|
export const GRADIENT_COLOR_SPACE_RE = /\s+in\s+(?:oklab|oklch|srgb(?:-linear)?|display-p3|a98-rgb|prophoto-rgb|rec2020|xyz(?:-d(?:50|65))?|hsl|hwb|lab|lch)/g;
|
|
32
|
-
export function stripGradientColorSpace(value) {
|
|
33
|
-
if (typeof value !== "string")
|
|
34
|
-
return value;
|
|
35
|
-
return value.replace(GRADIENT_COLOR_SPACE_RE, "");
|
|
36
|
-
}
|
|
37
32
|
const COLOR_MIX_HEX_RE = /color-mix\(in\s+\w+,\s*#([0-9a-fA-F]{3,8})\s+([\d.]+%?)\s*,\s*transparent\s*\)/g;
|
|
38
33
|
const COLOR_MIX_RGBA_RE = /color-mix\(in\s+\w+,\s*rgba?\((\d+)[\x09-\x0D\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*(?: |(?: \s*)?,)\s*(\d+)[\x09-\x0D\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]*(?: |(?: \s*)?,)\s*(\d+)(?:\s*[/,]\s*[\d.]+)?\)\s+([\d.]+%?)\s*,\s*transparent\s*\)/g;
|
|
39
34
|
function hexToRgb(hex) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { defineNitroPlugin } from "#nuxtseo/nitro";
|
|
2
|
+
import { collectAutoEjectTemplates, ejectCollectedTemplates } from "../util/auto-eject-lifecycle.js";
|
|
3
|
+
export default defineNitroPlugin((nitro) => {
|
|
4
|
+
if (!import.meta.dev)
|
|
5
|
+
return;
|
|
6
|
+
const hooks = nitro.hooks;
|
|
7
|
+
hooks.hook("render:html", (html, { event }) => collectAutoEjectTemplates(html, event));
|
|
8
|
+
hooks.hook("response", (_response, event) => ejectCollectedTemplates(event));
|
|
9
|
+
});
|
|
@@ -1,36 +1,8 @@
|
|
|
1
|
-
import { parse } from "devalue";
|
|
2
1
|
import { defineNitroPlugin } from "#nuxtseo/nitro";
|
|
3
|
-
import {
|
|
4
|
-
import { normaliseOptions } from "../util/options.js";
|
|
5
|
-
import { useOgImageRuntimeConfig } from "../utils.js";
|
|
6
|
-
const PAYLOAD_REGEX = /<script.+id="nuxt-og-image-options"[^>]*>(.+?)<\/script>/;
|
|
2
|
+
import { collectAutoEjectTemplates, ejectCollectedTemplates } from "../util/auto-eject-lifecycle.js";
|
|
7
3
|
export default defineNitroPlugin((nitro) => {
|
|
8
4
|
if (!import.meta.dev)
|
|
9
5
|
return;
|
|
10
|
-
nitro.hooks.hook("render:html", (html,
|
|
11
|
-
|
|
12
|
-
if (!payload)
|
|
13
|
-
return;
|
|
14
|
-
for (const options of parse(payload)) {
|
|
15
|
-
if (!options?.component)
|
|
16
|
-
continue;
|
|
17
|
-
const normalised = (() => {
|
|
18
|
-
try {
|
|
19
|
-
return normaliseOptions({ ...options });
|
|
20
|
-
} catch {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
})();
|
|
24
|
-
if (normalised?.component?.category === "community")
|
|
25
|
-
(ctx.event.context._ogImageAutoEject ||= []).push(normalised.component);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
nitro.hooks.hook("afterResponse", (event) => {
|
|
29
|
-
const components = event.context._ogImageAutoEject;
|
|
30
|
-
if (!components?.length)
|
|
31
|
-
return;
|
|
32
|
-
const runtimeConfig = useOgImageRuntimeConfig(event);
|
|
33
|
-
for (const component of components)
|
|
34
|
-
autoEjectCommunityTemplate(component, runtimeConfig);
|
|
35
|
-
});
|
|
6
|
+
nitro.hooks.hook("render:html", (html, { event }) => collectAutoEjectTemplates(html, event));
|
|
7
|
+
nitro.hooks.hook("afterResponse", ejectCollectedTemplates);
|
|
36
8
|
});
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { parse } from "devalue";
|
|
2
|
-
import { parseURL, withoutBase } from "ufo";
|
|
2
|
+
import { joinURL, parseURL, withoutBase, withQuery } from "ufo";
|
|
3
3
|
import { appendResponseHeader } from "#nuxtseo/h3";
|
|
4
|
-
import { defineNitroPlugin, useRuntimeConfig } from "#nuxtseo/nitro";
|
|
4
|
+
import { defineNitroPlugin, useRuntimeConfig, useStorage } from "#nuxtseo/nitro";
|
|
5
5
|
import { prerenderOptionsCache } from "#og-image-cache";
|
|
6
|
+
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
7
|
+
import { logger } from "../../logger.js";
|
|
6
8
|
import { isInternalRoute } from "../../shared.js";
|
|
9
|
+
import { getPublishKey, publishImage } from "../og-image/cache/publish.js";
|
|
7
10
|
import { createNitroRouteRuleMatcher } from "../util/kit.js";
|
|
8
11
|
import { toStorageKey } from "../util/storageKey.js";
|
|
12
|
+
import { useOgImageRuntimeConfig } from "../utils.js";
|
|
9
13
|
const PAYLOAD_REGEX = /<script.+id="nuxt-og-image-options"[^>]*>(.+?)<\/script>/;
|
|
10
14
|
const RE_SCRIPT_OPTIONS = /<script id="nuxt-og-image-options" type="application\/json">[\s\S]*?<\/script>/;
|
|
11
15
|
const RE_SCRIPT_OVERRIDES = /<script id="nuxt-og-image-overrides" type="application\/json">[\s\S]*?<\/script>/;
|
|
@@ -13,10 +17,21 @@ function getPayloadFromHtml(html) {
|
|
|
13
17
|
const match = String(html).match(PAYLOAD_REGEX);
|
|
14
18
|
return match ? String(match[1]) : null;
|
|
15
19
|
}
|
|
20
|
+
function replaceImageUrls(head, path, resolve) {
|
|
21
|
+
return head.map((entry) => entry.replace(/(<meta\b[^>]+\bcontent=")([^"]*)("[^>]*>)/g, (tag, start, url, end) => {
|
|
22
|
+
if (!/\b(?:property|name)="(?:og:image(?::url|:secure_url)?|twitter:image(?::src)?)"/.test(tag))
|
|
23
|
+
return tag;
|
|
24
|
+
const source = url.replace(/&/g, "&");
|
|
25
|
+
const urlPath = parseURL(source).pathname;
|
|
26
|
+
return urlPath?.replace(/,/g, "%2C") === path ? `${start}${resolve(source).replace(/&/g, "&")}${end}` : tag;
|
|
27
|
+
}));
|
|
28
|
+
}
|
|
16
29
|
export default defineNitroPlugin(async (nitro) => {
|
|
17
30
|
if (!import.meta.prerender)
|
|
18
31
|
return;
|
|
19
32
|
const routeRuleMatcher = createNitroRouteRuleMatcher();
|
|
33
|
+
const publishingPages = /* @__PURE__ */ new Set();
|
|
34
|
+
let publishWarningShown = false;
|
|
20
35
|
nitro.hooks.hook("render:html", async (html, ctx) => {
|
|
21
36
|
const { head, bodyAppend } = html;
|
|
22
37
|
const path = parseURL(ctx.event.path).pathname;
|
|
@@ -38,7 +53,57 @@ export default defineNitroPlugin(async (nitro) => {
|
|
|
38
53
|
}
|
|
39
54
|
const prerenderPaths = ctx.event.context._ogImagePrerenderPaths;
|
|
40
55
|
if (prerenderPaths) {
|
|
56
|
+
const config = useOgImageRuntimeConfig(ctx.event);
|
|
57
|
+
const publish = config.publish;
|
|
58
|
+
if (publish && !publishingPages.has(path)) {
|
|
59
|
+
publishingPages.add(path);
|
|
60
|
+
try {
|
|
61
|
+
for (const [ogKey, prerenderPath] of prerenderPaths) {
|
|
62
|
+
const extension = prerenderPath.split(".").pop();
|
|
63
|
+
if (!prerenderPath.includes("/_og/s/") || extension !== "png" && extension !== "jpeg" && extension !== "jpg" && extension !== "webp")
|
|
64
|
+
continue;
|
|
65
|
+
const opt = payloads.find(([key]) => key === ogKey)?.[1];
|
|
66
|
+
if (!opt)
|
|
67
|
+
continue;
|
|
68
|
+
const result = await publishImage({
|
|
69
|
+
storage: useStorage(),
|
|
70
|
+
mount: publish.storage,
|
|
71
|
+
baseURL: publish.baseURL,
|
|
72
|
+
key: getPublishKey(new URL(prerenderPath, getSiteConfig(ctx.event).url).href, config.defaults, publish.cacheVersion),
|
|
73
|
+
extension,
|
|
74
|
+
maxAgeSeconds: Number(opt.cacheMaxAgeSeconds ?? config.defaults.cacheMaxAgeSeconds),
|
|
75
|
+
timeoutMs: config.security.renderTimeout,
|
|
76
|
+
now: Date.now,
|
|
77
|
+
render: async () => {
|
|
78
|
+
if (opt.component === "PageScreenshot") {
|
|
79
|
+
opt._prerenderHtml = `<!DOCTYPE html><html ${(html.htmlAttrs || []).join(" ")}><head>${html.head.join("")}</head><body ${(html.bodyAttrs || []).join(" ")}>${[...html.bodyPrepend || [], ...html.body || [], ...html.bodyAppend].join("")}</body></html>`;
|
|
80
|
+
if (opt._hash)
|
|
81
|
+
await prerenderOptionsCache.setItem(toStorageKey("prerender", opt._hash), { ...opt, _path: pagePath });
|
|
82
|
+
}
|
|
83
|
+
const response = await nitro.localFetch(opt._query ? withQuery(prerenderPath, { _query: opt._query }) : prerenderPath);
|
|
84
|
+
if (!response.ok || !response.headers.get("content-type")?.startsWith("image/"))
|
|
85
|
+
throw new Error(`Image render failed: ${response.status}. ${(await response.text()).slice(0, 1e3)}`);
|
|
86
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
if (result._tag === "Published") {
|
|
90
|
+
html.head = replaceImageUrls(html.head, prerenderPath, () => result.url);
|
|
91
|
+
prerenderPaths.delete(ogKey);
|
|
92
|
+
} else if (!publishWarningShown) {
|
|
93
|
+
publishWarningShown = true;
|
|
94
|
+
logger.warn("[Nuxt OG Image] Publish storage is unavailable. Using local image URLs.", result.reason);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
} finally {
|
|
98
|
+
publishingPages.delete(path);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const app = useRuntimeConfig(ctx.event).app;
|
|
41
102
|
for (const prerenderPath of prerenderPaths.values()) {
|
|
103
|
+
if (app.cdnURL && prerenderPath.includes("/_og/s/")) {
|
|
104
|
+
const imageURL = joinURL(app.cdnURL, withoutBase(prerenderPath, app.baseURL));
|
|
105
|
+
html.head = replaceImageUrls(html.head, prerenderPath, (source) => `${imageURL}${parseURL(source).search || ""}`);
|
|
106
|
+
}
|
|
42
107
|
appendResponseHeader(ctx.event, "x-nitro-prerender", prerenderPath);
|
|
43
108
|
}
|
|
44
109
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { parseURL, withoutBase } from "ufo";
|
|
2
|
+
import { defineNitroPlugin } from "#nuxtseo/nitro";
|
|
3
|
+
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
4
|
+
import { logger } from "../../logger.js";
|
|
5
|
+
import { isInternalRoute } from "../../shared.js";
|
|
6
|
+
import { getPublishKey } from "../og-image/cache/publish.js";
|
|
7
|
+
import { getPublishedImageCache, getPublishedImageCacheKey } from "../og-image/cache/published.js";
|
|
8
|
+
import { useOgImageRuntimeConfig } from "../utils.js";
|
|
9
|
+
const IMAGE_META = /<meta\b[^>]+\b(?:property|name)="(?:og:image(?::url|:secure_url)?|twitter:image(?::src)?)"[^>]*>/g;
|
|
10
|
+
const CONTENT = /\bcontent="([^"]+)"/;
|
|
11
|
+
export default defineNitroPlugin((nitro) => {
|
|
12
|
+
if (import.meta.dev || import.meta.prerender)
|
|
13
|
+
return;
|
|
14
|
+
nitro.hooks.hook("render:html", (html, { event }) => {
|
|
15
|
+
if (isInternalRoute(parseURL(event.path).pathname))
|
|
16
|
+
return;
|
|
17
|
+
const config = useOgImageRuntimeConfig(event);
|
|
18
|
+
const publish = config.publish;
|
|
19
|
+
if (!publish)
|
|
20
|
+
return;
|
|
21
|
+
const siteURL = new URL(getSiteConfig(event).url);
|
|
22
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
23
|
+
const urls = new Set(html.head.flatMap((entry) => [...entry.matchAll(IMAGE_META)].map((match) => match[0].match(CONTENT)?.[1]).filter((url) => !!url)));
|
|
24
|
+
const cache = getPublishedImageCache(nitro);
|
|
25
|
+
for (const source of urls) {
|
|
26
|
+
try {
|
|
27
|
+
const url = new URL(source.replace(/&/g, "&"), siteURL);
|
|
28
|
+
const path = withoutBase(url.pathname, config.app.baseURL);
|
|
29
|
+
if (url.origin !== siteURL.origin || !path.startsWith("/_og/d/"))
|
|
30
|
+
continue;
|
|
31
|
+
const extension = path.split(".").pop();
|
|
32
|
+
if (extension !== "png" && extension !== "jpeg" && extension !== "jpg" && extension !== "webp")
|
|
33
|
+
continue;
|
|
34
|
+
const key = getPublishedImageCacheKey(publish, getPublishKey(url.href, config.defaults, publish.cacheVersion), extension);
|
|
35
|
+
const result = cache.get(key, Date.now());
|
|
36
|
+
if (result)
|
|
37
|
+
replacements.set(source, result.url.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">"));
|
|
38
|
+
} catch (err) {
|
|
39
|
+
logger.debug("[Nuxt OG Image] Published URL lookup failed. Keeping the app image URL.", err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
html.head = html.head.map((entry) => entry.replace(IMAGE_META, (tag) => tag.replace(CONTENT, (content, source) => replacements.has(source) ? `content="${replacements.get(source)}"` : content)));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
declare const _default:
|
|
1
|
+
declare const _default: import("nuxt/server").EventHandler<Promise<{
|
|
2
|
+
siteConfigUrl: string;
|
|
3
|
+
origin: string;
|
|
4
|
+
componentNames: any;
|
|
5
|
+
runtimeConfig: import("../../types.js").OgImageRuntimeConfig;
|
|
6
|
+
compatibility: any;
|
|
7
|
+
resolvedFonts: any;
|
|
8
|
+
availableFonts: any;
|
|
9
|
+
fontRequirements: any;
|
|
10
|
+
}>>;
|
|
2
11
|
export default _default;
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { defineEventHandler
|
|
1
|
+
import { defineEventHandler } from "nuxt/server";
|
|
2
2
|
import { componentNames } from "#og-image-virtual/component-names.mjs";
|
|
3
3
|
import compatibility from "#og-image/compatibility";
|
|
4
4
|
import { fontRequirements } from "#og-image/font-requirements";
|
|
5
5
|
import resolvedFonts from "#og-image/fonts";
|
|
6
6
|
import availableFonts from "#og-image/fonts-available";
|
|
7
|
-
import { getNitroOrigin } from "#site-config/server/composables";
|
|
8
7
|
import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
|
|
8
|
+
import { getOgImageOrigin } from "../util/origin.js";
|
|
9
9
|
import { useOgImageRuntimeConfig } from "../utils.js";
|
|
10
10
|
export default defineEventHandler(async (e) => {
|
|
11
|
-
|
|
11
|
+
e.res.headers.set("Content-Type", "application/json");
|
|
12
12
|
const runtimeConfig = useOgImageRuntimeConfig(e);
|
|
13
13
|
return {
|
|
14
14
|
siteConfigUrl: getSiteConfig(e).url,
|
|
15
|
-
origin:
|
|
15
|
+
origin: getOgImageOrigin(e),
|
|
16
16
|
componentNames,
|
|
17
17
|
runtimeConfig,
|
|
18
18
|
compatibility,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createError, defineEventHandler, getRequestURL, useRuntimeConfig } from "nuxt/server";
|
|
3
|
+
import { join } from "pathe";
|
|
4
|
+
import { withoutBase } from "ufo";
|
|
5
|
+
import { staticFontCacheDir } from "#og-image-virtual/build-dir.mjs";
|
|
6
|
+
import resolvedFonts from "#og-image/fonts";
|
|
7
|
+
import availableFonts from "#og-image/fonts-available";
|
|
8
|
+
export default defineEventHandler(async (event) => {
|
|
9
|
+
const path = withoutBase(getRequestURL(event).pathname, useRuntimeConfig().app.baseURL);
|
|
10
|
+
const font = [...resolvedFonts, ...availableFonts].find((face) => face.src === path || face.satoriSrc === path);
|
|
11
|
+
if (!font)
|
|
12
|
+
throw createError({ status: 404, statusText: "Not Found" });
|
|
13
|
+
const file = path === font.src && font.absolutePath ? font.absolutePath : join(staticFontCacheDir, path.slice("/_og-static-fonts/".length));
|
|
14
|
+
const data = await readFile(file);
|
|
15
|
+
return new Response(new Uint8Array(data), {
|
|
16
|
+
headers: {
|
|
17
|
+
"Content-Type": "application/octet-stream",
|
|
18
|
+
"Cache-Control": "no-store",
|
|
19
|
+
"Access-Control-Allow-Origin": "*"
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { RequestEvent } from 'nuxt/server';
|
|
2
|
+
export declare const OG_IMAGE_SECRET_PURPOSE = "nuxt-og-image:url-signing";
|
|
3
|
+
/** Resolve once per request before synchronous app and server URL helpers run. */
|
|
4
|
+
export declare function initializeOgImageSigning(event: Pick<RequestEvent, 'context'>): Promise<void>;
|
|
5
|
+
/** Only the image endpoints need the secret. Every other request can render without it. */
|
|
6
|
+
export declare function isOgImageEndpointPath(pathname: string, baseURL: string): boolean;
|
|
7
|
+
/** Log a missing or invalid secret once per server instance, with the fix. */
|
|
8
|
+
export declare function reportSigningFailure(failure: unknown): void;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { createError, deriveSecret, useRuntimeConfig } from "nuxt/server";
|
|
2
|
+
import { joinURL } from "ufo";
|
|
3
|
+
import { getCloudflareEnv } from "./util/cloudflare.js";
|
|
4
|
+
export const OG_IMAGE_SECRET_PURPOSE = "nuxt-og-image:url-signing";
|
|
5
|
+
let observed;
|
|
6
|
+
let warnedLegacy = false;
|
|
7
|
+
export async function initializeOgImageSigning(event) {
|
|
8
|
+
const config = useRuntimeConfig();
|
|
9
|
+
const security = config["nuxt-og-image"].security;
|
|
10
|
+
if (security.secret === false || import.meta.prerender && !security.strict)
|
|
11
|
+
return;
|
|
12
|
+
const cloudflareEnv = getCloudflareEnv(event);
|
|
13
|
+
const configRoot = config.appSecret;
|
|
14
|
+
const processRoot = globalThis.process?.env?.NUXT_OG_IMAGE_SECRET;
|
|
15
|
+
if (cloudflareEnv !== void 0) {
|
|
16
|
+
const root2 = resolveLegacyRoot(
|
|
17
|
+
cloudflareEnv.NUXT_APP_SECRET === void 0 ? configRoot : cloudflareEnv.NUXT_APP_SECRET,
|
|
18
|
+
cloudflareEnv.NUXT_OG_IMAGE_SECRET
|
|
19
|
+
);
|
|
20
|
+
const secret = observed !== void 0 && observed.root === root2 ? observed.secret : root2 === configRoot ? await deriveSecret(OG_IMAGE_SECRET_PURPOSE) : await deriveRootSecret(root2);
|
|
21
|
+
observed = { root: root2, configRoot, processRoot, secret };
|
|
22
|
+
event.context._ogImageSigningSecret = secret;
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (observed !== void 0 && configRoot === observed.configRoot && processRoot === observed.processRoot) {
|
|
26
|
+
event.context._ogImageSigningSecret = observed.secret;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const root = resolveLegacyRoot(configRoot, processRoot);
|
|
30
|
+
if (root !== configRoot) {
|
|
31
|
+
event.context._ogImageSigningSecret = await deriveRootSecret(root);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
event.context._ogImageSigningSecret = await deriveSecret(OG_IMAGE_SECRET_PURPOSE);
|
|
35
|
+
}
|
|
36
|
+
export function isOgImageEndpointPath(pathname, baseURL) {
|
|
37
|
+
return pathname.startsWith("/_og/") || pathname.startsWith(joinURL(baseURL, "_og/"));
|
|
38
|
+
}
|
|
39
|
+
let reportedFailure = false;
|
|
40
|
+
export function reportSigningFailure(failure) {
|
|
41
|
+
if (reportedFailure)
|
|
42
|
+
return;
|
|
43
|
+
reportedFailure = true;
|
|
44
|
+
const cause = failure instanceof Error ? failure.message : String(failure);
|
|
45
|
+
console.error([
|
|
46
|
+
"[nuxt-og-image] URL signing has no valid secret. OG image requests return 500 until you fix this. Pages still render.",
|
|
47
|
+
"Run `npx nuxt-og-image generate-secret`, then set NUXT_APP_SECRET in the production environment.",
|
|
48
|
+
"The secret must have at least 32 characters. To turn off signing, set `ogImage.security.secret` to false.",
|
|
49
|
+
`Cause: ${cause}`
|
|
50
|
+
].join("\n"));
|
|
51
|
+
}
|
|
52
|
+
function resolveLegacyRoot(root, legacy) {
|
|
53
|
+
if ((root === void 0 || root === "") && legacy !== void 0 && legacy !== "") {
|
|
54
|
+
if (!warnedLegacy) {
|
|
55
|
+
warnedLegacy = true;
|
|
56
|
+
console.warn("[nuxt-og-image] NUXT_OG_IMAGE_SECRET is deprecated. Rename it to NUXT_APP_SECRET. Existing signed URLs must be refreshed.");
|
|
57
|
+
}
|
|
58
|
+
return legacy;
|
|
59
|
+
}
|
|
60
|
+
return root;
|
|
61
|
+
}
|
|
62
|
+
async function deriveRootSecret(root) {
|
|
63
|
+
if (typeof root !== "string" || root.length < 32) {
|
|
64
|
+
throw createError({ status: 500, message: "Set NUXT_APP_SECRET to a string with at least 32 characters." });
|
|
65
|
+
}
|
|
66
|
+
const encoder = new TextEncoder();
|
|
67
|
+
const key = await crypto.subtle.importKey("raw", encoder.encode(root), "HKDF", false, ["deriveBits"]);
|
|
68
|
+
const bits = await crypto.subtle.deriveBits({
|
|
69
|
+
name: "HKDF",
|
|
70
|
+
hash: "SHA-256",
|
|
71
|
+
salt: encoder.encode("nuxt"),
|
|
72
|
+
info: encoder.encode(OG_IMAGE_SECRET_PURPOSE)
|
|
73
|
+
}, key, 256);
|
|
74
|
+
return Array.from(new Uint8Array(bits), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
75
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { RequestEvent } from 'nuxt/server';
|
|
2
|
+
import type { OgImageComponent } from '../../types.js';
|
|
3
|
+
export interface AutoEjectEvent {
|
|
4
|
+
context: RequestEvent['context'] & {
|
|
5
|
+
_ogImageAutoEject?: OgImageComponent[];
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export interface AutoEjectHtml {
|
|
9
|
+
head: string[];
|
|
10
|
+
bodyAppend: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare function collectAutoEjectTemplates(html: AutoEjectHtml, event: AutoEjectEvent): void;
|
|
13
|
+
export declare function ejectCollectedTemplates(event: AutoEjectEvent): void;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { parse } from "devalue";
|
|
2
|
+
import { useOgImageRuntimeConfig } from "../utils.js";
|
|
3
|
+
import { autoEjectCommunityTemplate } from "./auto-eject.js";
|
|
4
|
+
import { normaliseOptions } from "./options.js";
|
|
5
|
+
const PAYLOAD_REGEX = /<script.+id="nuxt-og-image-options"[^>]*>(.+?)<\/script>/;
|
|
6
|
+
export function collectAutoEjectTemplates(html, event) {
|
|
7
|
+
const payload = [...html.head, ...html.bodyAppend].join("\n").match(PAYLOAD_REGEX)?.[1];
|
|
8
|
+
if (!payload)
|
|
9
|
+
return;
|
|
10
|
+
for (const options of parse(payload)) {
|
|
11
|
+
if (!options?.component)
|
|
12
|
+
continue;
|
|
13
|
+
const normalised = (() => {
|
|
14
|
+
try {
|
|
15
|
+
return normaliseOptions({ ...options });
|
|
16
|
+
} catch {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
})();
|
|
20
|
+
if (normalised?.component?.category === "community")
|
|
21
|
+
(event.context._ogImageAutoEject ||= []).push(normalised.component);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export function ejectCollectedTemplates(event) {
|
|
25
|
+
const components = event.context._ogImageAutoEject;
|
|
26
|
+
if (!components?.length)
|
|
27
|
+
return;
|
|
28
|
+
const runtimeConfig = useOgImageRuntimeConfig(event);
|
|
29
|
+
for (const component of components)
|
|
30
|
+
autoEjectCommunityTemplate(component, runtimeConfig);
|
|
31
|
+
}
|
|
@@ -13,12 +13,13 @@ function warnCacheBackendUnreachable(ctx, baseCacheKey, e) {
|
|
|
13
13
|
}
|
|
14
14
|
export async function useOgImageBufferCache(ctx, options) {
|
|
15
15
|
const maxAge = Number(options.cacheMaxAgeSeconds);
|
|
16
|
-
const intentionallyEnabled = !import.meta.dev && !import.meta.prerender && maxAge > 0;
|
|
16
|
+
const intentionallyEnabled = options.baseCacheKey !== false && !import.meta.dev && !import.meta.prerender && maxAge > 0;
|
|
17
17
|
let enabled = intentionallyEnabled;
|
|
18
|
-
const cache = prefixStorage(useStorage(), withTrailingSlash(options.baseCacheKey
|
|
18
|
+
const cache = options.baseCacheKey === false ? void 0 : prefixStorage(useStorage(), withTrailingSlash(options.baseCacheKey));
|
|
19
19
|
const key = toStorageKey("image", ctx.key);
|
|
20
20
|
let cachedItem = false;
|
|
21
|
-
|
|
21
|
+
let cacheExpiresAt = 0;
|
|
22
|
+
if (enabled && cache) {
|
|
22
23
|
const hasItem = await cache.hasItem(key).catch((e) => {
|
|
23
24
|
enabled = false;
|
|
24
25
|
warnCacheBackendUnreachable(ctx, options.baseCacheKey, e);
|
|
@@ -36,6 +37,7 @@ export async function useOgImageBufferCache(ctx, options) {
|
|
|
36
37
|
void err;
|
|
37
38
|
});
|
|
38
39
|
} else if (expiresAt > Date.now()) {
|
|
40
|
+
cacheExpiresAt = expiresAt;
|
|
39
41
|
cachedItem = Buffer.from(value, "base64");
|
|
40
42
|
if (handleCacheHeaders(ctx.e, {
|
|
41
43
|
modifiedTime: new Date(headers["last-modified"]),
|
|
@@ -61,11 +63,15 @@ export async function useOgImageBufferCache(ctx, options) {
|
|
|
61
63
|
return {
|
|
62
64
|
enabled,
|
|
63
65
|
cachedItem,
|
|
66
|
+
get expiresAt() {
|
|
67
|
+
return cacheExpiresAt;
|
|
68
|
+
},
|
|
64
69
|
async update(item) {
|
|
65
70
|
setHeader(ctx.e, "X-OG-Cache", enabled ? "MISS" : "DISABLED");
|
|
66
|
-
if (!enabled)
|
|
71
|
+
if (!enabled || !cache)
|
|
67
72
|
return;
|
|
68
73
|
const value = Buffer.from(item).toString("base64");
|
|
74
|
+
cacheExpiresAt = Date.now() + maxAge * 1e3;
|
|
69
75
|
const headers = {
|
|
70
76
|
// avoid multi-tenancy cache issues
|
|
71
77
|
"Vary": "accept-encoding, host",
|
|
@@ -77,8 +83,8 @@ export async function useOgImageBufferCache(ctx, options) {
|
|
|
77
83
|
await cache.setItem(key, {
|
|
78
84
|
value,
|
|
79
85
|
headers,
|
|
80
|
-
expiresAt:
|
|
81
|
-
}).catch((err) => logger.warn(`[Nuxt OG Image] Failed to write cache for key "${key}": ${err?.message || err}`));
|
|
86
|
+
expiresAt: cacheExpiresAt
|
|
87
|
+
}, { ttl: Math.max(maxAge, 60) }).catch((err) => logger.warn(`[Nuxt OG Image] Failed to write cache for key "${key}": ${err?.message || err}`));
|
|
82
88
|
}
|
|
83
89
|
};
|
|
84
90
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RequestEvent } from 'nuxt/server';
|
|
2
2
|
export type CloudflareEnv = Record<string, unknown>;
|
|
3
|
-
export declare function getCloudflareEnv(event?:
|
|
3
|
+
export declare function getCloudflareEnv(event?: Pick<RequestEvent, 'context'>): CloudflareEnv | undefined;
|