nuxt-og-image 6.7.2 → 6.7.4
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.cjs +7 -7
- package/dist/chunks/tw4.mjs +7 -7
- package/dist/chunks/uno.cjs +6 -6
- package/dist/chunks/uno.mjs +6 -6
- package/dist/cli.cjs +5 -12
- package/dist/cli.mjs +3 -10
- package/dist/devtools/lib/og-image/shared/urlEncoding.ts +5 -4
- package/dist/module.cjs +5 -5
- package/dist/module.json +1 -1
- package/dist/module.mjs +5 -5
- package/dist/runtime/app/plugins/__zero-runtime/og-image-canonical-urls.server.js +4 -6
- package/dist/runtime/app/plugins/__zero-runtime/route-rule-og-image.server.js +4 -6
- package/dist/runtime/app/plugins/og-image-canonical-urls.server.d.ts +1 -2
- package/dist/runtime/app/plugins/og-image-canonical-urls.server.js +1 -7
- package/dist/runtime/app/plugins/route-rule-og-image.server.d.ts +1 -2
- package/dist/runtime/app/plugins/route-rule-og-image.server.js +1 -7
- package/dist/runtime/app/utils/plugins.d.ts +2 -3
- package/dist/runtime/app/utils/plugins.js +5 -5
- package/dist/runtime/server/og-image/context.js +6 -5
- package/dist/runtime/server/routes/resolve.js +3 -2
- package/dist/runtime/server/util/cache.js +5 -4
- package/dist/runtime/server/util/kit.js +2 -2
- package/dist/runtime/server/util/query.d.ts +7 -0
- package/dist/runtime/server/util/query.js +4 -0
- package/dist/runtime/shared/hash.d.ts +10 -0
- package/dist/runtime/shared/hash.js +5 -0
- package/dist/runtime/shared/urlEncoding.d.ts +3 -2
- package/dist/runtime/shared/urlEncoding.js +9 -9
- package/dist/shared/{nuxt-og-image.BCK8Adm4.cjs → nuxt-og-image.BRBtdkUp.cjs} +570 -81
- package/dist/shared/{nuxt-og-image.Pfj5JkJd.mjs → nuxt-og-image.Bv8VSGTj.mjs} +499 -10
- package/dist/shared/nuxt-og-image.CJa2KCie.mjs +189 -0
- package/dist/shared/nuxt-og-image.CMYbz66P.cjs +193 -0
- package/package.json +46 -44
- package/types/virtual.d.ts +9 -0
- package/dist/shared/nuxt-og-image.DQkJQHaN.cjs +0 -689
- package/dist/shared/nuxt-og-image.LgUGeaz-.mjs +0 -660
|
@@ -3,19 +3,21 @@ import * as fs from 'node:fs';
|
|
|
3
3
|
import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, rmSync } from 'node:fs';
|
|
4
4
|
import { readdir, readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
|
-
import { useNuxt,
|
|
6
|
+
import { resolvePath, useNuxt, addTemplate, updateTemplates, addTypeTemplate, defineNuxtModule, createResolver, addImports, hasNuxtModule, hasNuxtModuleCompatibility, getNuxtModuleVersion, addVitePlugin, addBuildPlugin, addServerHandler, addPlugin, addComponentsDir, getNuxtVersion, addServerPlugin } from '@nuxt/kit';
|
|
7
7
|
import { defu } from 'defu';
|
|
8
|
+
import { fnv1a64Base36 } from 'fnv1a-64';
|
|
8
9
|
import { installNuxtSiteConfig } from 'nuxt-site-config/kit';
|
|
9
|
-
import {
|
|
10
|
-
import { join, dirname, relative, isAbsolute, resolve, basename } from 'pathe';
|
|
10
|
+
import { basename, join, dirname, relative, isAbsolute, resolve } from 'pathe';
|
|
11
11
|
import { readPackageJSON } from 'pkg-types';
|
|
12
|
-
import { isCI, isAgent } from 'std-env';
|
|
12
|
+
import { isCI, isAgent, hasTTY } from 'std-env';
|
|
13
13
|
import { parseAndWalk } from 'oxc-walker';
|
|
14
|
-
import {
|
|
14
|
+
import { resolveNitroPreset, getNuxtModuleOptions, isNuxtGenerate } from 'nuxtseo-shared/kit';
|
|
15
|
+
import { logger } from '../../dist/runtime/logger.js';
|
|
16
|
+
import { Launcher } from 'chrome-launcher';
|
|
17
|
+
import { detectPackageManager, addDependency } from 'nypm';
|
|
15
18
|
import { setupDevToolsUI as setupDevToolsUI$1, setupDevToolsRpc } from 'nuxtseo-shared/devtools';
|
|
16
19
|
import { createStorage } from 'unstorage';
|
|
17
20
|
import fsDriver from 'unstorage/drivers/fs-lite';
|
|
18
|
-
import { logger } from '../../dist/runtime/logger.js';
|
|
19
21
|
import { resolveColorMix, GRADIENT_COLOR_SPACE_RE, splitCssDeclarations } from '../../dist/runtime/server/og-image/utils/css.js';
|
|
20
22
|
import MagicString from 'magic-string';
|
|
21
23
|
import { stripLiteral } from 'strip-literal';
|
|
@@ -26,7 +28,277 @@ import { ofetch } from 'ofetch';
|
|
|
26
28
|
import { parse as parse$1, walkSync, ELEMENT_NODE as ELEMENT_NODE$2, renderSync } from 'ultrahtml';
|
|
27
29
|
import { RE_MATCH_EMOJIS, getEmojiCodePoint, getEmojiIconNames } from '../../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js';
|
|
28
30
|
import { resolveModulePath } from 'exsolve';
|
|
29
|
-
import {
|
|
31
|
+
import { p as promptFontsMigration } from './nuxt-og-image.CJa2KCie.mjs';
|
|
32
|
+
|
|
33
|
+
const isUndefinedOrTruthy = (v) => typeof v === "undefined" || v !== false;
|
|
34
|
+
function checkLocalChrome() {
|
|
35
|
+
if (isCI)
|
|
36
|
+
return false;
|
|
37
|
+
let hasChromeLocally = false;
|
|
38
|
+
try {
|
|
39
|
+
hasChromeLocally = !!Launcher.getFirstInstallation();
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
return hasChromeLocally;
|
|
43
|
+
}
|
|
44
|
+
async function hasResolvableDependency(dep) {
|
|
45
|
+
return await resolvePath(dep, { fallbackToOriginal: true }).catch(() => null).then((r) => r && r !== dep);
|
|
46
|
+
}
|
|
47
|
+
const VALID_RENDERER_SUFFIXES = ["satori", "browser", "takumi"];
|
|
48
|
+
const RE_RENDERER_SUFFIX_CI = /\.?(satori|browser|takumi)$/i;
|
|
49
|
+
const RE_RENDERER_SUFFIX = /(Satori|Browser|Takumi)$/;
|
|
50
|
+
const RE_LEGACY_SUFFIX = /-legacy$/;
|
|
51
|
+
const RE_WHITESPACE = /\s+/;
|
|
52
|
+
function getRendererFromFilename(filepath) {
|
|
53
|
+
const filename = basename(filepath).replace(".vue", "");
|
|
54
|
+
for (const suffix of VALID_RENDERER_SUFFIXES) {
|
|
55
|
+
if (filename.endsWith(`.${suffix}`))
|
|
56
|
+
return suffix;
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
const OGIMAGE_PREFIXES = [
|
|
61
|
+
{ prefix: "OgImageCommunity", overlapWord: "Community" },
|
|
62
|
+
{ prefix: "OgImageTemplate", overlapWord: "Template" },
|
|
63
|
+
{ prefix: "OgImage", overlapWord: "Image" }
|
|
64
|
+
];
|
|
65
|
+
function getRegisteredBaseNames(registeredPascalName) {
|
|
66
|
+
const stripped = registeredPascalName.replace(RE_RENDERER_SUFFIX_CI, "").replace(RE_RENDERER_SUFFIX, "");
|
|
67
|
+
const names = [];
|
|
68
|
+
for (const { prefix, overlapWord } of OGIMAGE_PREFIXES) {
|
|
69
|
+
if (!stripped.startsWith(prefix))
|
|
70
|
+
continue;
|
|
71
|
+
const withoutPrefix = stripped.slice(prefix.length);
|
|
72
|
+
if (withoutPrefix) {
|
|
73
|
+
names.push(withoutPrefix);
|
|
74
|
+
if (withoutPrefix !== overlapWord)
|
|
75
|
+
names.push(overlapWord + withoutPrefix);
|
|
76
|
+
} else {
|
|
77
|
+
names.push(overlapWord);
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
if (names.length === 0)
|
|
82
|
+
names.push(stripped);
|
|
83
|
+
return names;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const NodeRuntime = {
|
|
87
|
+
// node-server runtime
|
|
88
|
+
browser: "on-demand",
|
|
89
|
+
// this gets changed build start
|
|
90
|
+
resvg: "node",
|
|
91
|
+
satori: "node",
|
|
92
|
+
takumi: "node",
|
|
93
|
+
sharp: "node",
|
|
94
|
+
// will be disabled if they're missing the dependency
|
|
95
|
+
emoji: "local"
|
|
96
|
+
// can bundle icons, no size constraints
|
|
97
|
+
};
|
|
98
|
+
const NodeDevRuntime = {
|
|
99
|
+
...NodeRuntime,
|
|
100
|
+
resvg: "node-dev",
|
|
101
|
+
// use worker to prevent crashes from killing process
|
|
102
|
+
takumi: "node-dev"
|
|
103
|
+
// use worker to prevent crashes from killing process
|
|
104
|
+
};
|
|
105
|
+
const NodePrerenderRuntime = {
|
|
106
|
+
...NodeRuntime,
|
|
107
|
+
resvg: "node-dev",
|
|
108
|
+
// use worker to prevent crashes from killing prerender process
|
|
109
|
+
takumi: "node-dev"
|
|
110
|
+
// use worker to prevent crashes from killing prerender process
|
|
111
|
+
};
|
|
112
|
+
const cloudflare = {
|
|
113
|
+
browser: "cloudflare",
|
|
114
|
+
resvg: "wasm",
|
|
115
|
+
satori: "wasm",
|
|
116
|
+
takumi: "wasm",
|
|
117
|
+
sharp: false,
|
|
118
|
+
emoji: "fetch",
|
|
119
|
+
// edge size limits - use API instead of bundling 24MB icons
|
|
120
|
+
wasm: {
|
|
121
|
+
esmImport: true
|
|
122
|
+
// Do NOT set lazy here — Nitro's cloudflare preset uses lazy: false because
|
|
123
|
+
// CF Workers require WASM modules to be imported eagerly via top-level await
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const awsLambda = {
|
|
127
|
+
browser: false,
|
|
128
|
+
resvg: "node",
|
|
129
|
+
satori: "node",
|
|
130
|
+
takumi: "node",
|
|
131
|
+
sharp: false,
|
|
132
|
+
// 0.33.x has issues
|
|
133
|
+
emoji: "local"
|
|
134
|
+
// serverless has larger size limits
|
|
135
|
+
};
|
|
136
|
+
const WebContainer = {
|
|
137
|
+
browser: false,
|
|
138
|
+
resvg: "wasm-fs",
|
|
139
|
+
satori: "wasm-fs",
|
|
140
|
+
takumi: "wasm",
|
|
141
|
+
sharp: false,
|
|
142
|
+
emoji: "fetch"
|
|
143
|
+
// webcontainer has size constraints
|
|
144
|
+
};
|
|
145
|
+
const RuntimeCompatibility = {
|
|
146
|
+
"nitro-dev": NodeDevRuntime,
|
|
147
|
+
"nitro-prerender": NodePrerenderRuntime,
|
|
148
|
+
"node-server": NodeRuntime,
|
|
149
|
+
"node-cluster": NodeRuntime,
|
|
150
|
+
"node-listener": NodeRuntime,
|
|
151
|
+
"bun": NodeRuntime,
|
|
152
|
+
"deno-server": NodeRuntime,
|
|
153
|
+
"deno-deploy": NodeRuntime,
|
|
154
|
+
// node-server extending presets
|
|
155
|
+
"alwaysdata": NodeRuntime,
|
|
156
|
+
"cleavr": NodeRuntime,
|
|
157
|
+
"digital-ocean": NodeRuntime,
|
|
158
|
+
"edgio": NodeRuntime,
|
|
159
|
+
"flight-control": NodeRuntime,
|
|
160
|
+
"heroku": NodeRuntime,
|
|
161
|
+
"iis-handler": NodeRuntime,
|
|
162
|
+
"iis-node": NodeRuntime,
|
|
163
|
+
"koyeb": NodeRuntime,
|
|
164
|
+
"platform-sh": NodeRuntime,
|
|
165
|
+
"render-com": NodeRuntime,
|
|
166
|
+
"zeabur": NodeRuntime,
|
|
167
|
+
"zerops": NodeRuntime,
|
|
168
|
+
"stackblitz": WebContainer,
|
|
169
|
+
"codesandbox": WebContainer,
|
|
170
|
+
"aws-amplify": awsLambda,
|
|
171
|
+
"aws-lambda": awsLambda,
|
|
172
|
+
"azure": awsLambda,
|
|
173
|
+
"azure-functions": awsLambda,
|
|
174
|
+
"azure-swa": awsLambda,
|
|
175
|
+
"netlify": awsLambda,
|
|
176
|
+
"netlify-edge": {
|
|
177
|
+
browser: false,
|
|
178
|
+
resvg: "wasm",
|
|
179
|
+
satori: "node",
|
|
180
|
+
takumi: "wasm",
|
|
181
|
+
sharp: false,
|
|
182
|
+
emoji: "fetch",
|
|
183
|
+
// edge size limits
|
|
184
|
+
wasm: {
|
|
185
|
+
rollup: {
|
|
186
|
+
targetEnv: "auto-inline",
|
|
187
|
+
sync: ["@resvg/resvg-wasm/index_bg.wasm"]
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
"firebase": awsLambda,
|
|
192
|
+
"firebase-app-hosting": awsLambda,
|
|
193
|
+
"genezio": awsLambda,
|
|
194
|
+
"stormkit": awsLambda,
|
|
195
|
+
"vercel": awsLambda,
|
|
196
|
+
"vercel-edge": {
|
|
197
|
+
browser: false,
|
|
198
|
+
resvg: "wasm",
|
|
199
|
+
satori: "wasm",
|
|
200
|
+
takumi: "wasm",
|
|
201
|
+
sharp: false,
|
|
202
|
+
emoji: "fetch",
|
|
203
|
+
// edge size limits - bundling 24MB icons not viable
|
|
204
|
+
wasm: {
|
|
205
|
+
// lowers workers kb size
|
|
206
|
+
esmImport: true,
|
|
207
|
+
lazy: true
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
"cloudflare-pages": cloudflare,
|
|
211
|
+
"cloudflare-pages-static": cloudflare,
|
|
212
|
+
"cloudflare": cloudflare,
|
|
213
|
+
"cloudflare-module": cloudflare,
|
|
214
|
+
"cloudflare-durable": cloudflare
|
|
215
|
+
};
|
|
216
|
+
function resolveOgImagePreset(nitroConfig) {
|
|
217
|
+
const nuxt = useNuxt();
|
|
218
|
+
if (nuxt.options.dev)
|
|
219
|
+
return "nitro-dev";
|
|
220
|
+
if (nuxt.options.nitro.static)
|
|
221
|
+
return "nitro-prerender";
|
|
222
|
+
return resolveNitroPreset(nitroConfig);
|
|
223
|
+
}
|
|
224
|
+
function getPresetNitroPresetCompatibility(target) {
|
|
225
|
+
const normalizedTarget = target.replace(RE_LEGACY_SUFFIX, "");
|
|
226
|
+
let compatibility = RuntimeCompatibility[normalizedTarget];
|
|
227
|
+
if (!compatibility) {
|
|
228
|
+
logger.warn(`Unknown Nitro preset "${target}" \u2014 falling back to node-server compatibility. Set ogImage.compatibility.runtime to override.`);
|
|
229
|
+
compatibility = NodeRuntime;
|
|
230
|
+
}
|
|
231
|
+
return compatibility;
|
|
232
|
+
}
|
|
233
|
+
async function applyNitroPresetCompatibility(nitroConfig, options) {
|
|
234
|
+
const target = resolveOgImagePreset(nitroConfig);
|
|
235
|
+
const compatibility = getPresetNitroPresetCompatibility(target);
|
|
236
|
+
const { resolve, detectedRenderers } = options;
|
|
237
|
+
const satoriEnabled = detectedRenderers.has("satori");
|
|
238
|
+
const browserEnabled = detectedRenderers.has("browser");
|
|
239
|
+
const takumiEnabled = detectedRenderers.has("takumi");
|
|
240
|
+
for (const renderer of detectedRenderers) {
|
|
241
|
+
if (!compatibility[renderer]) {
|
|
242
|
+
logger.warn(`Renderer "${renderer}" detected but not supported on "${target}" preset. OG images using .${renderer}.vue components may fail.`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const emptyMock = await resolve.resolvePath("./runtime/mock/empty");
|
|
246
|
+
nitroConfig.alias["#og-image/renderers/satori"] = satoriEnabled ? await resolve.resolvePath("./runtime/server/og-image/satori/renderer") : emptyMock;
|
|
247
|
+
nitroConfig.alias["#og-image/renderers/browser"] = browserEnabled ? await resolve.resolvePath("./runtime/server/og-image/browser/renderer") : emptyMock;
|
|
248
|
+
nitroConfig.alias["#og-image/renderers/takumi"] = takumiEnabled ? await resolve.resolvePath("./runtime/server/og-image/takumi/renderer") : emptyMock;
|
|
249
|
+
const resolvedCompatibility = { ...options.metadata || {} };
|
|
250
|
+
async function applyBinding(key) {
|
|
251
|
+
let binding = options.compatibility?.[key];
|
|
252
|
+
if (typeof binding === "undefined")
|
|
253
|
+
binding = compatibility[key];
|
|
254
|
+
const isRendererBinding = key === "browser" || key === "satori" || key === "takumi";
|
|
255
|
+
if (isRendererBinding && !detectedRenderers.has(key)) {
|
|
256
|
+
binding = false;
|
|
257
|
+
}
|
|
258
|
+
resolvedCompatibility[key] = binding;
|
|
259
|
+
if (!binding && isRendererBinding) {
|
|
260
|
+
nitroConfig.alias[`#og-image/renderers/${key}`] = emptyMock;
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
[`#og-image/bindings/${key}`]: !binding ? emptyMock : await resolve.resolvePath(`./runtime/server/og-image/bindings/${key}/${binding}`)
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const satoriPkgDir = await resolve.resolvePath("satori/package.json").then((p) => p.replace("/package.json", ""));
|
|
267
|
+
const yogaWasmPath = `${satoriPkgDir}/yoga.wasm`;
|
|
268
|
+
nitroConfig.alias = defu(
|
|
269
|
+
{ "#og-image/yoga-wasm": `${yogaWasmPath}?module` },
|
|
270
|
+
await applyBinding("browser"),
|
|
271
|
+
await applyBinding("satori"),
|
|
272
|
+
await applyBinding("takumi"),
|
|
273
|
+
await applyBinding("resvg"),
|
|
274
|
+
await applyBinding("sharp"),
|
|
275
|
+
nitroConfig.alias || {}
|
|
276
|
+
);
|
|
277
|
+
if (Object.values(resolvedCompatibility).includes("wasm")) {
|
|
278
|
+
nitroConfig.experimental = nitroConfig.experimental || {};
|
|
279
|
+
nitroConfig.experimental.wasm = true;
|
|
280
|
+
}
|
|
281
|
+
nitroConfig.rollupConfig = nitroConfig.rollupConfig || {};
|
|
282
|
+
nitroConfig.wasm = defu(compatibility.wasm, nitroConfig.wasm);
|
|
283
|
+
nitroConfig.virtual["#og-image/compatibility"] = () => `export default ${JSON.stringify(resolvedCompatibility)}`;
|
|
284
|
+
addTemplate({
|
|
285
|
+
filename: "nuxt-og-image/compatibility.mjs",
|
|
286
|
+
getContents() {
|
|
287
|
+
return `export default ${JSON.stringify(resolvedCompatibility)}`;
|
|
288
|
+
},
|
|
289
|
+
options: { mode: "server" }
|
|
290
|
+
});
|
|
291
|
+
return resolvedCompatibility;
|
|
292
|
+
}
|
|
293
|
+
async function ensureDependencies(dep, nuxt = useNuxt()) {
|
|
294
|
+
const { ensureDependencyInstalled } = await import('nypm');
|
|
295
|
+
return Promise.all(dep.map((d) => {
|
|
296
|
+
return ensureDependencyInstalled(d, {
|
|
297
|
+
cwd: nuxt.options.rootDir,
|
|
298
|
+
dev: true
|
|
299
|
+
});
|
|
300
|
+
}));
|
|
301
|
+
}
|
|
30
302
|
|
|
31
303
|
const RE_REFLECT_HAS_MINIFIED = /Reflect\.has\(([\w$]+),([\w$]+)\)\?Reflect\.get\(\1,\2,([\w$]+)\):Reflect\.get\(([\w$]+),\2,\3\)/g;
|
|
32
304
|
const RE_WASM_IMPORT = /import\("(\.\/wasm\/[^"]+\.wasm)"\)/g;
|
|
@@ -183,6 +455,198 @@ function patchWebAssemblyInstantiate(code) {
|
|
|
183
455
|
return code;
|
|
184
456
|
}
|
|
185
457
|
|
|
458
|
+
const TAKUMI_CORE_PACKAGE = "@takumi-rs/core";
|
|
459
|
+
const TAKUMI_WASM_PACKAGE = "@takumi-rs/wasm";
|
|
460
|
+
const PROVIDER_DEPENDENCIES = [
|
|
461
|
+
{
|
|
462
|
+
name: "satori",
|
|
463
|
+
description: "SVG-based renderer using Satori",
|
|
464
|
+
bindings: {
|
|
465
|
+
"node": [
|
|
466
|
+
{ name: "satori", description: "HTML to SVG renderer" },
|
|
467
|
+
{ name: "@resvg/resvg-js", description: "SVG to PNG converter (native)" }
|
|
468
|
+
],
|
|
469
|
+
"wasm": [
|
|
470
|
+
{ name: "satori", description: "HTML to SVG renderer" },
|
|
471
|
+
{ name: "@resvg/resvg-wasm", description: "SVG to PNG converter (WASM)" }
|
|
472
|
+
],
|
|
473
|
+
"wasm-fs": [
|
|
474
|
+
{ name: "satori", description: "HTML to SVG renderer" },
|
|
475
|
+
{ name: "@resvg/resvg-wasm", description: "SVG to PNG converter (WASM)" }
|
|
476
|
+
]
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: "takumi",
|
|
481
|
+
description: "Rust-based high-performance renderer (recommended)",
|
|
482
|
+
bindings: {
|
|
483
|
+
"node": [
|
|
484
|
+
{ name: TAKUMI_CORE_PACKAGE, description: "Native Takumi renderer" }
|
|
485
|
+
],
|
|
486
|
+
"wasm": [
|
|
487
|
+
{ name: TAKUMI_WASM_PACKAGE, description: "WASM Takumi renderer" }
|
|
488
|
+
],
|
|
489
|
+
"wasm-fs": [
|
|
490
|
+
{ name: TAKUMI_WASM_PACKAGE, description: "WASM Takumi renderer" }
|
|
491
|
+
]
|
|
492
|
+
}
|
|
493
|
+
},
|
|
494
|
+
{
|
|
495
|
+
name: "browser",
|
|
496
|
+
description: "Browser-based screenshot renderer",
|
|
497
|
+
bindings: {
|
|
498
|
+
node: [
|
|
499
|
+
{ name: "playwright-core", description: "Headless browser automation" }
|
|
500
|
+
]
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
];
|
|
504
|
+
async function getInstalledProviders() {
|
|
505
|
+
const installed = [];
|
|
506
|
+
for (const provider of PROVIDER_DEPENDENCIES) {
|
|
507
|
+
if (provider.bindings.node) {
|
|
508
|
+
const allNodeInstalled = await Promise.all(
|
|
509
|
+
provider.bindings.node.map((dep) => hasResolvableDependency(dep.name))
|
|
510
|
+
);
|
|
511
|
+
if (allNodeInstalled.every(Boolean)) {
|
|
512
|
+
installed.push({ provider: provider.name, binding: "node" });
|
|
513
|
+
continue;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (provider.bindings.wasm) {
|
|
517
|
+
const allWasmInstalled = await Promise.all(
|
|
518
|
+
provider.bindings.wasm.map((dep) => hasResolvableDependency(dep.name))
|
|
519
|
+
);
|
|
520
|
+
if (allWasmInstalled.every(Boolean)) {
|
|
521
|
+
installed.push({ provider: provider.name, binding: "wasm" });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return installed;
|
|
526
|
+
}
|
|
527
|
+
async function getMissingDependencies(provider, binding = "node") {
|
|
528
|
+
const missing = await getMissingProviderDependencyDefinitions(provider, binding);
|
|
529
|
+
return missing.map((d) => d.name);
|
|
530
|
+
}
|
|
531
|
+
async function getMissingProviderDependencyDefinitions(provider, binding = "node") {
|
|
532
|
+
const providerDef = PROVIDER_DEPENDENCIES.find((p) => p.name === provider);
|
|
533
|
+
if (!providerDef)
|
|
534
|
+
return [];
|
|
535
|
+
const deps = providerDef.bindings[binding] || providerDef.bindings.node || [];
|
|
536
|
+
const missing = [];
|
|
537
|
+
for (const dep of deps) {
|
|
538
|
+
if (!await hasResolvableDependency(dep.name))
|
|
539
|
+
missing.push(dep);
|
|
540
|
+
}
|
|
541
|
+
return missing;
|
|
542
|
+
}
|
|
543
|
+
function getDependencyDefinitions(provider, binding = "node") {
|
|
544
|
+
const providerDef = PROVIDER_DEPENDENCIES.find((p) => p.name === provider);
|
|
545
|
+
if (!providerDef)
|
|
546
|
+
return [];
|
|
547
|
+
return providerDef.bindings[binding] || providerDef.bindings.node || [];
|
|
548
|
+
}
|
|
549
|
+
function getDependencyInstallSpec(dep) {
|
|
550
|
+
return dep.installSpec || dep.name;
|
|
551
|
+
}
|
|
552
|
+
async function getMissingDependencyInstallSpecs(provider, binding = "node") {
|
|
553
|
+
const missing = await getMissingProviderDependencyDefinitions(provider, binding);
|
|
554
|
+
return missing.map(getDependencyInstallSpec);
|
|
555
|
+
}
|
|
556
|
+
function getProviderDependencyInstallSpecs(provider, binding = "node") {
|
|
557
|
+
return getDependencyDefinitions(provider, binding).map(getDependencyInstallSpec);
|
|
558
|
+
}
|
|
559
|
+
function getRecommendedBindingFromPreset(provider) {
|
|
560
|
+
const preset = resolveOgImagePreset();
|
|
561
|
+
const compatibility = getPresetNitroPresetCompatibility(preset);
|
|
562
|
+
return getRecommendedBinding(provider, compatibility);
|
|
563
|
+
}
|
|
564
|
+
function getRecommendedBinding(provider, compatibility) {
|
|
565
|
+
if (provider === "satori") {
|
|
566
|
+
const resvgBinding = compatibility.resvg;
|
|
567
|
+
if (resvgBinding === "wasm-fs")
|
|
568
|
+
return "wasm-fs";
|
|
569
|
+
if (resvgBinding === "wasm")
|
|
570
|
+
return "wasm";
|
|
571
|
+
return "node";
|
|
572
|
+
}
|
|
573
|
+
if (provider === "takumi") {
|
|
574
|
+
const takumiBinding = compatibility.takumi;
|
|
575
|
+
if (takumiBinding === "wasm")
|
|
576
|
+
return "wasm";
|
|
577
|
+
return "node";
|
|
578
|
+
}
|
|
579
|
+
if (provider === "browser") {
|
|
580
|
+
return "node";
|
|
581
|
+
}
|
|
582
|
+
return "node";
|
|
583
|
+
}
|
|
584
|
+
async function ensureProviderDependencies(provider, binding, nuxt) {
|
|
585
|
+
const missingDeps = await getMissingProviderDependencyDefinitions(provider, binding);
|
|
586
|
+
if (missingDeps.length === 0)
|
|
587
|
+
return { success: true, installed: [] };
|
|
588
|
+
const missingInstallSpecs = missingDeps.map(getDependencyInstallSpec);
|
|
589
|
+
const pm = await detectPackageManager(nuxt.options.rootDir);
|
|
590
|
+
const pmName = pm?.name || "npm";
|
|
591
|
+
logger.info(`Installing ${provider} dependencies: ${missingInstallSpecs.join(", ")}`);
|
|
592
|
+
const installed = [];
|
|
593
|
+
for (const dep of missingDeps) {
|
|
594
|
+
const installSpec = getDependencyInstallSpec(dep);
|
|
595
|
+
const success = await addDependency(installSpec, {
|
|
596
|
+
cwd: nuxt.options.rootDir,
|
|
597
|
+
dev: false
|
|
598
|
+
}).then(() => {
|
|
599
|
+
installed.push(installSpec);
|
|
600
|
+
return true;
|
|
601
|
+
}).catch(() => {
|
|
602
|
+
logger.error(`Failed to install ${installSpec}. Run manually: ${pmName} add ${installSpec}`);
|
|
603
|
+
return false;
|
|
604
|
+
});
|
|
605
|
+
if (!success)
|
|
606
|
+
return { success: false, installed };
|
|
607
|
+
}
|
|
608
|
+
return { success: true, installed };
|
|
609
|
+
}
|
|
610
|
+
function canPromptInteractively(env = {
|
|
611
|
+
hasTTY,
|
|
612
|
+
hasStdinTTY: Boolean(process.stdin?.isTTY),
|
|
613
|
+
isAgent,
|
|
614
|
+
isCI
|
|
615
|
+
}) {
|
|
616
|
+
return env.hasTTY && env.hasStdinTTY && !env.isAgent && !env.isCI;
|
|
617
|
+
}
|
|
618
|
+
async function promptForRendererSelection() {
|
|
619
|
+
logger.info("Welcome to Nuxt OG Image! No renderer dependencies detected.");
|
|
620
|
+
const renderer = await logger.prompt("Which renderer would you like to use?", {
|
|
621
|
+
type: "select",
|
|
622
|
+
options: PROVIDER_DEPENDENCIES.map((p) => p.name),
|
|
623
|
+
initial: "takumi"
|
|
624
|
+
});
|
|
625
|
+
return renderer || "takumi";
|
|
626
|
+
}
|
|
627
|
+
async function validateProviderSetup(renderer, compatibility) {
|
|
628
|
+
const issues = [];
|
|
629
|
+
const binding = getRecommendedBinding(renderer, compatibility);
|
|
630
|
+
const missing = await getMissingDependencyInstallSpecs(renderer, binding);
|
|
631
|
+
if (missing.length > 0) {
|
|
632
|
+
issues.push(`Missing ${renderer} dependencies: ${missing.join(", ")}`);
|
|
633
|
+
}
|
|
634
|
+
if (renderer === "satori") {
|
|
635
|
+
const hasResvgNode = await hasResolvableDependency("@resvg/resvg-js");
|
|
636
|
+
const hasResvgWasm = await hasResolvableDependency("@resvg/resvg-wasm");
|
|
637
|
+
if (!hasResvgNode && !hasResvgWasm) {
|
|
638
|
+
issues.push("Satori requires either @resvg/resvg-js (node) or @resvg/resvg-wasm to render PNGs");
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (renderer === "browser") {
|
|
642
|
+
const hasPlaywright = await hasResolvableDependency("playwright-core");
|
|
643
|
+
if (!hasPlaywright && binding === "node") {
|
|
644
|
+
issues.push("Browser renderer requires playwright-core");
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return { valid: issues.length === 0, issues };
|
|
648
|
+
}
|
|
649
|
+
|
|
186
650
|
function setupDevHandler(options, resolve, getDetectedRenderers, getCompatibilityMeta = () => ({}), nuxt = useNuxt()) {
|
|
187
651
|
nuxt.hooks.hook("nitro:init", async (nitro) => {
|
|
188
652
|
const detectedRenderers = new Set(getDetectedRenderers());
|
|
@@ -3760,7 +4224,7 @@ async function onInstall(nuxt) {
|
|
|
3760
4224
|
if (installedProviders2.length === 0) {
|
|
3761
4225
|
throw new Error(
|
|
3762
4226
|
`[nuxt-og-image] No OG image provider dependencies found. Install a provider before running in CI:
|
|
3763
|
-
npm add ${
|
|
4227
|
+
npm add ${TAKUMI_CORE_PACKAGE} # for takumi
|
|
3764
4228
|
npm add @resvg/resvg-js satori yoga-wasm-web # for satori
|
|
3765
4229
|
npm add playwright-core # for browser
|
|
3766
4230
|
See: https://nuxtseo.com/og-image/getting-started`
|
|
@@ -4074,6 +4538,15 @@ declare module '#og-image/font-requirements' {
|
|
|
4074
4538
|
export const hasNuxtFonts: boolean
|
|
4075
4539
|
}
|
|
4076
4540
|
|
|
4541
|
+
declare module '#og-image/island-hash' {
|
|
4542
|
+
export function getIslandHash(input: {
|
|
4543
|
+
name: string
|
|
4544
|
+
props?: Record<string, any> | string | null
|
|
4545
|
+
context?: Record<string, any>
|
|
4546
|
+
source?: string
|
|
4547
|
+
}): string
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4077
4550
|
declare module '#og-image-virtual/unocss-config.mjs' {
|
|
4078
4551
|
export const theme: Record<string, any>
|
|
4079
4552
|
}
|
|
@@ -4852,7 +5325,7 @@ ${familyInfo}`);
|
|
|
4852
5325
|
logger.debug(`Using ${renderer} renderer`);
|
|
4853
5326
|
} else {
|
|
4854
5327
|
ogImageComponentCtx.detectedRenderers.add("takumi");
|
|
4855
|
-
logger.warn(`No OG image renderer dependency detected. Defaulting to \`takumi\` (non-interactive environment). Install \`${
|
|
5328
|
+
logger.warn(`No OG image renderer dependency detected. Defaulting to \`takumi\` (non-interactive environment). Install \`${TAKUMI_CORE_PACKAGE}\`, or add a renderer component (e.g. components/OgImage/Default.satori.vue) to choose explicitly.`);
|
|
4856
5329
|
}
|
|
4857
5330
|
}
|
|
4858
5331
|
const availableRenderers = /* @__PURE__ */ new Set();
|
|
@@ -4958,7 +5431,7 @@ ${familyInfo}`);
|
|
|
4958
5431
|
const credits = componentFile.split("\n").find((line) => line.startsWith(" * @credits"))?.replace("* @credits", "").trim();
|
|
4959
5432
|
const propNames = extractPropNamesFromVue(componentFile);
|
|
4960
5433
|
ogImageComponentCtx.components.push({
|
|
4961
|
-
hash:
|
|
5434
|
+
hash: fnv1a64Base36(componentFile),
|
|
4962
5435
|
pascalName: component.pascalName,
|
|
4963
5436
|
kebabName: component.kebabName,
|
|
4964
5437
|
path: component.filePath,
|
|
@@ -5043,6 +5516,22 @@ Run: npx nuxt-og-image migrate v6`;
|
|
|
5043
5516
|
nuxt.options.nitro.virtual["#og-image-virtual/component-names.mjs"] = () => {
|
|
5044
5517
|
return `export const componentNames = ${JSON.stringify(ogImageComponentCtx.components)}`;
|
|
5045
5518
|
};
|
|
5519
|
+
nuxt.options.nitro.virtual["#og-image/island-hash"] = () => {
|
|
5520
|
+
const islandHashFile = join(nuxt.options.appDir, "island-hash.js");
|
|
5521
|
+
const [nuxtMajor = 0, nuxtMinor = 0] = getNuxtVersion(nuxt).split(".").map((v) => Number.parseInt(v));
|
|
5522
|
+
if (nuxtMajor > 4 || nuxtMajor === 4 && nuxtMinor >= 5)
|
|
5523
|
+
return `export { getIslandHash } from '${islandHashFile}'`;
|
|
5524
|
+
if (existsSync(islandHashFile)) {
|
|
5525
|
+
return `import { computeIslandHash } from '${islandHashFile}'
|
|
5526
|
+
export function getIslandHash({ name, props, context, source }) {
|
|
5527
|
+
return computeIslandHash(name, props ?? {}, context ?? {}, source)
|
|
5528
|
+
}`;
|
|
5529
|
+
}
|
|
5530
|
+
return `import { hash } from 'ohash'
|
|
5531
|
+
export function getIslandHash({ name, props, context, source }) {
|
|
5532
|
+
return hash([name, props ?? {}, context ?? {}, source]).replace(/[-_]/g, '')
|
|
5533
|
+
}`;
|
|
5534
|
+
};
|
|
5046
5535
|
nuxt.options.nitro.publicAssets ||= [];
|
|
5047
5536
|
const staticFontsTtfDir = join(nuxt.options.buildDir, "cache", "og-image", "fonts-ttf");
|
|
5048
5537
|
nuxt.options.nitro.publicAssets.push({
|