nuxt-og-image 6.6.0 → 6.7.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.
Files changed (42) hide show
  1. package/README.md +2 -0
  2. package/dist/chunks/tw4.cjs +7 -7
  3. package/dist/chunks/tw4.mjs +7 -7
  4. package/dist/chunks/uno.cjs +6 -6
  5. package/dist/chunks/uno.mjs +6 -6
  6. package/dist/cli.cjs +12 -5
  7. package/dist/cli.mjs +10 -3
  8. package/dist/module.cjs +5 -5
  9. package/dist/module.d.cts +5 -3
  10. package/dist/module.d.mts +5 -3
  11. package/dist/module.d.ts +5 -3
  12. package/dist/module.json +1 -1
  13. package/dist/module.mjs +5 -5
  14. package/dist/runtime/server/og-image/bindings/font-assets/cloudflare.d.ts +1 -1
  15. package/dist/runtime/server/og-image/bindings/font-assets/cloudflare.js +6 -1
  16. package/dist/runtime/server/og-image/bindings/font-assets/dev-prerender.js +13 -6
  17. package/dist/runtime/server/og-image/bindings/font-assets/external-url.d.ts +36 -0
  18. package/dist/runtime/server/og-image/bindings/font-assets/external-url.js +47 -0
  19. package/dist/runtime/server/og-image/bindings/font-assets/node.d.ts +1 -1
  20. package/dist/runtime/server/og-image/bindings/font-assets/node.js +14 -7
  21. package/dist/runtime/server/og-image/bindings/takumi/node-dev.d.ts +2 -1
  22. package/dist/runtime/server/og-image/bindings/takumi/node-dev.js +10 -8
  23. package/dist/runtime/server/og-image/bindings/takumi/node.d.ts +2 -1
  24. package/dist/runtime/server/og-image/bindings/takumi/node.js +2 -1
  25. package/dist/runtime/server/og-image/bindings/takumi/resource-urls.d.ts +2 -0
  26. package/dist/runtime/server/og-image/bindings/takumi/resource-urls.js +91 -0
  27. package/dist/runtime/server/og-image/bindings/takumi/wasm.d.ts +2 -1
  28. package/dist/runtime/server/og-image/bindings/takumi/wasm.js +2 -1
  29. package/dist/runtime/server/og-image/fonts.d.ts +4 -0
  30. package/dist/runtime/server/og-image/fonts.js +21 -5
  31. package/dist/runtime/server/og-image/satori/instances.d.ts +1 -2
  32. package/dist/runtime/server/og-image/takumi/renderer.js +61 -19
  33. package/dist/runtime/server/util/ssrf.d.ts +10 -0
  34. package/dist/runtime/server/util/ssrf.js +9 -1
  35. package/dist/runtime/types.d.ts +13 -0
  36. package/dist/shared/{nuxt-og-image.CfTPCtaS.cjs → nuxt-og-image.BCK8Adm4.cjs} +181 -555
  37. package/dist/shared/nuxt-og-image.DQkJQHaN.cjs +689 -0
  38. package/dist/shared/nuxt-og-image.LgUGeaz-.mjs +660 -0
  39. package/dist/shared/{nuxt-og-image.DdbTs-xp.mjs → nuxt-og-image.Pfj5JkJd.mjs} +122 -496
  40. package/package.json +46 -42
  41. package/dist/shared/nuxt-og-image.CJa2KCie.mjs +0 -189
  42. package/dist/shared/nuxt-og-image.CMYbz66P.cjs +0 -193
@@ -0,0 +1,689 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const magicast = require('magicast');
5
+ const helpers = require('magicast/helpers');
6
+ const pathe = require('pathe');
7
+ const logger_js = require('../../dist/runtime/logger.js');
8
+ const nypm = require('nypm');
9
+ const stdEnv = require('std-env');
10
+ const kit = require('@nuxt/kit');
11
+ const defu = require('defu');
12
+ const kit$1 = require('nuxtseo-shared/kit');
13
+ const chromeLauncher = require('chrome-launcher');
14
+
15
+ function parseFontString(font) {
16
+ const parts = font.split(":");
17
+ if (parts.length === 3) {
18
+ return {
19
+ name: parts[0] || font,
20
+ style: parts[1] === "ital" ? "italic" : "normal",
21
+ weight: Number.parseInt(parts[2] || "") || 400
22
+ };
23
+ }
24
+ return {
25
+ name: parts[0] || font,
26
+ weight: Number.parseInt(parts[1] || "") || 400,
27
+ style: "normal"
28
+ };
29
+ }
30
+ function groupFontsByFamily(fonts) {
31
+ const families = /* @__PURE__ */ new Map();
32
+ for (const font of fonts) {
33
+ const parsed = parseFontString(font);
34
+ const existing = families.get(parsed.name);
35
+ if (existing) {
36
+ existing.weights.add(parsed.weight);
37
+ existing.styles.add(parsed.style);
38
+ } else {
39
+ families.set(parsed.name, {
40
+ weights: /* @__PURE__ */ new Set([parsed.weight]),
41
+ styles: /* @__PURE__ */ new Set([parsed.style])
42
+ });
43
+ }
44
+ }
45
+ return Array.from(families.entries(), ([name, { weights, styles }]) => ({
46
+ name,
47
+ weights: [...weights].toSorted((a, b) => a - b),
48
+ styles: [...styles]
49
+ }));
50
+ }
51
+ async function migrateFontsConfig(rootDir) {
52
+ const configPaths = [
53
+ "nuxt.config.ts",
54
+ "nuxt.config.js",
55
+ "nuxt.config.mjs"
56
+ ];
57
+ let configPath;
58
+ for (const p of configPaths) {
59
+ const fullPath = pathe.join(rootDir, p);
60
+ if (fs.existsSync(fullPath)) {
61
+ configPath = fullPath;
62
+ break;
63
+ }
64
+ }
65
+ if (!configPath) {
66
+ return { migrated: false, message: "No nuxt.config found" };
67
+ }
68
+ const mod = await magicast.loadFile(configPath);
69
+ const config = mod.exports.default;
70
+ if (!config?.ogImage?.fonts) {
71
+ return { migrated: false, message: "No ogImage.fonts config found" };
72
+ }
73
+ const oldFonts = config.ogImage.fonts;
74
+ if (!Array.isArray(oldFonts) || oldFonts.length === 0) {
75
+ return { migrated: false, message: "ogImage.fonts is empty or invalid" };
76
+ }
77
+ const groupedFonts = groupFontsByFamily(oldFonts);
78
+ if (!config.fonts) {
79
+ config.fonts = {};
80
+ }
81
+ if (!config.fonts.families) {
82
+ config.fonts.families = [];
83
+ }
84
+ const existingFamilies = config.fonts.families;
85
+ for (const font of groupedFonts) {
86
+ const existing = existingFamilies.find((f) => f.name === font.name);
87
+ if (existing) {
88
+ const existingWeights = new Set(existing.weights || []);
89
+ for (const w of font.weights) {
90
+ existingWeights.add(w);
91
+ }
92
+ existing.weights = [...existingWeights].toSorted((a, b) => a - b);
93
+ if (font.styles.includes("italic")) {
94
+ const existingStyles = new Set(existing.styles || ["normal"]);
95
+ existingStyles.add("italic");
96
+ existing.styles = [...existingStyles];
97
+ }
98
+ existing.global = true;
99
+ } else {
100
+ const family = {
101
+ name: font.name,
102
+ weights: font.weights,
103
+ global: true
104
+ };
105
+ if (font.styles.includes("italic")) {
106
+ family.styles = font.styles;
107
+ }
108
+ existingFamilies.push(family);
109
+ }
110
+ }
111
+ helpers.addNuxtModule(mod, "@nuxt/fonts");
112
+ delete config.ogImage.fonts;
113
+ if (Object.keys(config.ogImage).length === 0) {
114
+ delete config.ogImage;
115
+ }
116
+ await magicast.writeFile(mod, configPath);
117
+ const familyNames = groupedFonts.map((f) => f.name).join(", ");
118
+ return {
119
+ migrated: true,
120
+ message: `Migrated fonts (${familyNames}) from ogImage.fonts to @nuxt/fonts`
121
+ };
122
+ }
123
+ async function migrateDefaultsComponent(rootDir) {
124
+ const configPaths = [
125
+ "nuxt.config.ts",
126
+ "nuxt.config.js",
127
+ "nuxt.config.mjs"
128
+ ];
129
+ let configPath;
130
+ for (const cp of configPaths) {
131
+ const fullPath = pathe.join(rootDir, cp);
132
+ if (fs.existsSync(fullPath)) {
133
+ configPath = fullPath;
134
+ break;
135
+ }
136
+ }
137
+ if (!configPath)
138
+ return { migrated: false, componentName: null, message: "No nuxt.config found" };
139
+ const mod = await magicast.loadFile(configPath);
140
+ const config = mod.exports.default;
141
+ if (!config?.ogImage?.defaults?.component)
142
+ return { migrated: false, componentName: null, message: "No defaults.component found" };
143
+ const componentName = String(config.ogImage.defaults.component);
144
+ delete config.ogImage.defaults.component;
145
+ if (config.ogImage.defaults && Object.keys(config.ogImage.defaults).length === 0)
146
+ delete config.ogImage.defaults;
147
+ if (config.ogImage && Object.keys(config.ogImage).length === 0)
148
+ delete config.ogImage;
149
+ await magicast.writeFile(mod, configPath);
150
+ return { migrated: true, componentName, message: `Removed defaults.component: '${componentName}'` };
151
+ }
152
+ async function promptFontsMigration(rootDir) {
153
+ logger_js.logger.info("");
154
+ logger_js.logger.info("Detected deprecated ogImage.fonts configuration.");
155
+ logger_js.logger.info("");
156
+ logger_js.logger.info("Inter fonts now work out of the box without any configuration.");
157
+ logger_js.logger.info("For custom fonts, use @nuxt/fonts:");
158
+ logger_js.logger.info("");
159
+ logger_js.logger.info("Before:");
160
+ logger_js.logger.info(" ogImage: {");
161
+ logger_js.logger.info(" fonts: ['Inter:400', 'Inter:700']");
162
+ logger_js.logger.info(" }");
163
+ logger_js.logger.info("");
164
+ logger_js.logger.info("After (Inter only): Remove the config entirely \u2014 it works by default.");
165
+ logger_js.logger.info("");
166
+ logger_js.logger.info("After (custom fonts):");
167
+ logger_js.logger.info(" modules: ['@nuxt/fonts', 'nuxt-og-image'],");
168
+ logger_js.logger.info(" fonts: {");
169
+ logger_js.logger.info(" families: [");
170
+ logger_js.logger.info(" { name: 'Roboto', weights: [400, 700], global: true }");
171
+ logger_js.logger.info(" ]");
172
+ logger_js.logger.info(" }");
173
+ logger_js.logger.info("");
174
+ const migrate = await logger_js.logger.prompt("Automatically migrate nuxt.config?", {
175
+ type: "confirm",
176
+ initial: true
177
+ });
178
+ if (migrate) {
179
+ const result = await migrateFontsConfig(rootDir).catch((err) => {
180
+ logger_js.logger.error(`Migration failed: ${err.message}`);
181
+ return { migrated: false, message: err.message };
182
+ });
183
+ if (result.migrated) {
184
+ logger_js.logger.success(result.message);
185
+ logger_js.logger.info("");
186
+ logger_js.logger.info("Please install @nuxt/fonts:");
187
+ logger_js.logger.info(" npm add @nuxt/fonts");
188
+ } else {
189
+ logger_js.logger.warn(`Could not migrate: ${result.message}`);
190
+ logger_js.logger.info("Please migrate manually.");
191
+ }
192
+ } else {
193
+ logger_js.logger.info("Skipping automatic migration. Please migrate manually.");
194
+ }
195
+ }
196
+
197
+ const isUndefinedOrTruthy = (v) => typeof v === "undefined" || v !== false;
198
+ function checkLocalChrome() {
199
+ if (stdEnv.isCI)
200
+ return false;
201
+ let hasChromeLocally = false;
202
+ try {
203
+ hasChromeLocally = !!chromeLauncher.Launcher.getFirstInstallation();
204
+ } catch {
205
+ }
206
+ return hasChromeLocally;
207
+ }
208
+ async function hasResolvableDependency(dep) {
209
+ return await kit.resolvePath(dep, { fallbackToOriginal: true }).catch(() => null).then((r) => r && r !== dep);
210
+ }
211
+ const VALID_RENDERER_SUFFIXES = ["satori", "browser", "takumi"];
212
+ const RE_RENDERER_SUFFIX_CI = /\.?(satori|browser|takumi)$/i;
213
+ const RE_RENDERER_SUFFIX = /(Satori|Browser|Takumi)$/;
214
+ const RE_LEGACY_SUFFIX = /-legacy$/;
215
+ const RE_WHITESPACE = /\s+/;
216
+ function getRendererFromFilename(filepath) {
217
+ const filename = pathe.basename(filepath).replace(".vue", "");
218
+ for (const suffix of VALID_RENDERER_SUFFIXES) {
219
+ if (filename.endsWith(`.${suffix}`))
220
+ return suffix;
221
+ }
222
+ return null;
223
+ }
224
+ const OGIMAGE_PREFIXES = [
225
+ { prefix: "OgImageCommunity", overlapWord: "Community" },
226
+ { prefix: "OgImageTemplate", overlapWord: "Template" },
227
+ { prefix: "OgImage", overlapWord: "Image" }
228
+ ];
229
+ function getRegisteredBaseNames(registeredPascalName) {
230
+ const stripped = registeredPascalName.replace(RE_RENDERER_SUFFIX_CI, "").replace(RE_RENDERER_SUFFIX, "");
231
+ const names = [];
232
+ for (const { prefix, overlapWord } of OGIMAGE_PREFIXES) {
233
+ if (!stripped.startsWith(prefix))
234
+ continue;
235
+ const withoutPrefix = stripped.slice(prefix.length);
236
+ if (withoutPrefix) {
237
+ names.push(withoutPrefix);
238
+ if (withoutPrefix !== overlapWord)
239
+ names.push(overlapWord + withoutPrefix);
240
+ } else {
241
+ names.push(overlapWord);
242
+ }
243
+ break;
244
+ }
245
+ if (names.length === 0)
246
+ names.push(stripped);
247
+ return names;
248
+ }
249
+
250
+ const NodeRuntime = {
251
+ // node-server runtime
252
+ browser: "on-demand",
253
+ // this gets changed build start
254
+ resvg: "node",
255
+ satori: "node",
256
+ takumi: "node",
257
+ sharp: "node",
258
+ // will be disabled if they're missing the dependency
259
+ emoji: "local"
260
+ // can bundle icons, no size constraints
261
+ };
262
+ const NodeDevRuntime = {
263
+ ...NodeRuntime,
264
+ resvg: "node-dev",
265
+ // use worker to prevent crashes from killing process
266
+ takumi: "node-dev"
267
+ // use worker to prevent crashes from killing process
268
+ };
269
+ const NodePrerenderRuntime = {
270
+ ...NodeRuntime,
271
+ resvg: "node-dev",
272
+ // use worker to prevent crashes from killing prerender process
273
+ takumi: "node-dev"
274
+ // use worker to prevent crashes from killing prerender process
275
+ };
276
+ const cloudflare = {
277
+ browser: "cloudflare",
278
+ resvg: "wasm",
279
+ satori: "wasm",
280
+ takumi: "wasm",
281
+ sharp: false,
282
+ emoji: "fetch",
283
+ // edge size limits - use API instead of bundling 24MB icons
284
+ wasm: {
285
+ esmImport: true
286
+ // Do NOT set lazy here — Nitro's cloudflare preset uses lazy: false because
287
+ // CF Workers require WASM modules to be imported eagerly via top-level await
288
+ }
289
+ };
290
+ const awsLambda = {
291
+ browser: false,
292
+ resvg: "node",
293
+ satori: "node",
294
+ takumi: "node",
295
+ sharp: false,
296
+ // 0.33.x has issues
297
+ emoji: "local"
298
+ // serverless has larger size limits
299
+ };
300
+ const WebContainer = {
301
+ browser: false,
302
+ resvg: "wasm-fs",
303
+ satori: "wasm-fs",
304
+ takumi: "wasm",
305
+ sharp: false,
306
+ emoji: "fetch"
307
+ // webcontainer has size constraints
308
+ };
309
+ const RuntimeCompatibility = {
310
+ "nitro-dev": NodeDevRuntime,
311
+ "nitro-prerender": NodePrerenderRuntime,
312
+ "node-server": NodeRuntime,
313
+ "node-cluster": NodeRuntime,
314
+ "node-listener": NodeRuntime,
315
+ "bun": NodeRuntime,
316
+ "deno-server": NodeRuntime,
317
+ "deno-deploy": NodeRuntime,
318
+ // node-server extending presets
319
+ "alwaysdata": NodeRuntime,
320
+ "cleavr": NodeRuntime,
321
+ "digital-ocean": NodeRuntime,
322
+ "edgio": NodeRuntime,
323
+ "flight-control": NodeRuntime,
324
+ "heroku": NodeRuntime,
325
+ "iis-handler": NodeRuntime,
326
+ "iis-node": NodeRuntime,
327
+ "koyeb": NodeRuntime,
328
+ "platform-sh": NodeRuntime,
329
+ "render-com": NodeRuntime,
330
+ "zeabur": NodeRuntime,
331
+ "zerops": NodeRuntime,
332
+ "stackblitz": WebContainer,
333
+ "codesandbox": WebContainer,
334
+ "aws-amplify": awsLambda,
335
+ "aws-lambda": awsLambda,
336
+ "azure": awsLambda,
337
+ "azure-functions": awsLambda,
338
+ "azure-swa": awsLambda,
339
+ "netlify": awsLambda,
340
+ "netlify-edge": {
341
+ browser: false,
342
+ resvg: "wasm",
343
+ satori: "node",
344
+ takumi: "wasm",
345
+ sharp: false,
346
+ emoji: "fetch",
347
+ // edge size limits
348
+ wasm: {
349
+ rollup: {
350
+ targetEnv: "auto-inline",
351
+ sync: ["@resvg/resvg-wasm/index_bg.wasm"]
352
+ }
353
+ }
354
+ },
355
+ "firebase": awsLambda,
356
+ "firebase-app-hosting": awsLambda,
357
+ "genezio": awsLambda,
358
+ "stormkit": awsLambda,
359
+ "vercel": awsLambda,
360
+ "vercel-edge": {
361
+ browser: false,
362
+ resvg: "wasm",
363
+ satori: "wasm",
364
+ takumi: "wasm",
365
+ sharp: false,
366
+ emoji: "fetch",
367
+ // edge size limits - bundling 24MB icons not viable
368
+ wasm: {
369
+ // lowers workers kb size
370
+ esmImport: true,
371
+ lazy: true
372
+ }
373
+ },
374
+ "cloudflare-pages": cloudflare,
375
+ "cloudflare-pages-static": cloudflare,
376
+ "cloudflare": cloudflare,
377
+ "cloudflare-module": cloudflare,
378
+ "cloudflare-durable": cloudflare
379
+ };
380
+ function resolveOgImagePreset(nitroConfig) {
381
+ const nuxt = kit.useNuxt();
382
+ if (nuxt.options.dev)
383
+ return "nitro-dev";
384
+ if (nuxt.options.nitro.static)
385
+ return "nitro-prerender";
386
+ return kit$1.resolveNitroPreset(nitroConfig);
387
+ }
388
+ function getPresetNitroPresetCompatibility(target) {
389
+ const normalizedTarget = target.replace(RE_LEGACY_SUFFIX, "");
390
+ let compatibility = RuntimeCompatibility[normalizedTarget];
391
+ if (!compatibility) {
392
+ logger_js.logger.warn(`Unknown Nitro preset "${target}" \u2014 falling back to node-server compatibility. Set ogImage.compatibility.runtime to override.`);
393
+ compatibility = NodeRuntime;
394
+ }
395
+ return compatibility;
396
+ }
397
+ async function applyNitroPresetCompatibility(nitroConfig, options) {
398
+ const target = resolveOgImagePreset(nitroConfig);
399
+ const compatibility = getPresetNitroPresetCompatibility(target);
400
+ const { resolve, detectedRenderers } = options;
401
+ const satoriEnabled = detectedRenderers.has("satori");
402
+ const browserEnabled = detectedRenderers.has("browser");
403
+ const takumiEnabled = detectedRenderers.has("takumi");
404
+ for (const renderer of detectedRenderers) {
405
+ if (!compatibility[renderer]) {
406
+ logger_js.logger.warn(`Renderer "${renderer}" detected but not supported on "${target}" preset. OG images using .${renderer}.vue components may fail.`);
407
+ }
408
+ }
409
+ const emptyMock = await resolve.resolvePath("./runtime/mock/empty");
410
+ nitroConfig.alias["#og-image/renderers/satori"] = satoriEnabled ? await resolve.resolvePath("./runtime/server/og-image/satori/renderer") : emptyMock;
411
+ nitroConfig.alias["#og-image/renderers/browser"] = browserEnabled ? await resolve.resolvePath("./runtime/server/og-image/browser/renderer") : emptyMock;
412
+ nitroConfig.alias["#og-image/renderers/takumi"] = takumiEnabled ? await resolve.resolvePath("./runtime/server/og-image/takumi/renderer") : emptyMock;
413
+ const resolvedCompatibility = { ...options.metadata || {} };
414
+ async function applyBinding(key) {
415
+ let binding = options.compatibility?.[key];
416
+ if (typeof binding === "undefined")
417
+ binding = compatibility[key];
418
+ const isRendererBinding = key === "browser" || key === "satori" || key === "takumi";
419
+ if (isRendererBinding && !detectedRenderers.has(key)) {
420
+ binding = false;
421
+ }
422
+ resolvedCompatibility[key] = binding;
423
+ if (!binding && isRendererBinding) {
424
+ nitroConfig.alias[`#og-image/renderers/${key}`] = emptyMock;
425
+ }
426
+ return {
427
+ [`#og-image/bindings/${key}`]: !binding ? emptyMock : await resolve.resolvePath(`./runtime/server/og-image/bindings/${key}/${binding}`)
428
+ };
429
+ }
430
+ const satoriPkgDir = await resolve.resolvePath("satori/package.json").then((p) => p.replace("/package.json", ""));
431
+ const yogaWasmPath = `${satoriPkgDir}/yoga.wasm`;
432
+ nitroConfig.alias = defu.defu(
433
+ { "#og-image/yoga-wasm": `${yogaWasmPath}?module` },
434
+ await applyBinding("browser"),
435
+ await applyBinding("satori"),
436
+ await applyBinding("takumi"),
437
+ await applyBinding("resvg"),
438
+ await applyBinding("sharp"),
439
+ nitroConfig.alias || {}
440
+ );
441
+ if (Object.values(resolvedCompatibility).includes("wasm")) {
442
+ nitroConfig.experimental = nitroConfig.experimental || {};
443
+ nitroConfig.experimental.wasm = true;
444
+ }
445
+ nitroConfig.rollupConfig = nitroConfig.rollupConfig || {};
446
+ nitroConfig.wasm = defu.defu(compatibility.wasm, nitroConfig.wasm);
447
+ nitroConfig.virtual["#og-image/compatibility"] = () => `export default ${JSON.stringify(resolvedCompatibility)}`;
448
+ kit.addTemplate({
449
+ filename: "nuxt-og-image/compatibility.mjs",
450
+ getContents() {
451
+ return `export default ${JSON.stringify(resolvedCompatibility)}`;
452
+ },
453
+ options: { mode: "server" }
454
+ });
455
+ return resolvedCompatibility;
456
+ }
457
+ async function ensureDependencies(dep, nuxt = kit.useNuxt()) {
458
+ const { ensureDependencyInstalled } = await import('nypm');
459
+ return Promise.all(dep.map((d) => {
460
+ return ensureDependencyInstalled(d, {
461
+ cwd: nuxt.options.rootDir,
462
+ dev: true
463
+ });
464
+ }));
465
+ }
466
+
467
+ const TAKUMI_INSTALL_TAG = "rc";
468
+ const TAKUMI_CORE_PACKAGE = "@takumi-rs/core";
469
+ const TAKUMI_WASM_PACKAGE = "@takumi-rs/wasm";
470
+ const TAKUMI_CORE_INSTALL_SPEC = `${TAKUMI_CORE_PACKAGE}@${TAKUMI_INSTALL_TAG}`;
471
+ const TAKUMI_WASM_INSTALL_SPEC = `${TAKUMI_WASM_PACKAGE}@${TAKUMI_INSTALL_TAG}`;
472
+ const PROVIDER_DEPENDENCIES = [
473
+ {
474
+ name: "satori",
475
+ description: "SVG-based renderer using Satori",
476
+ bindings: {
477
+ "node": [
478
+ { name: "satori", description: "HTML to SVG renderer" },
479
+ { name: "@resvg/resvg-js", description: "SVG to PNG converter (native)" }
480
+ ],
481
+ "wasm": [
482
+ { name: "satori", description: "HTML to SVG renderer" },
483
+ { name: "@resvg/resvg-wasm", description: "SVG to PNG converter (WASM)" }
484
+ ],
485
+ "wasm-fs": [
486
+ { name: "satori", description: "HTML to SVG renderer" },
487
+ { name: "@resvg/resvg-wasm", description: "SVG to PNG converter (WASM)" }
488
+ ]
489
+ }
490
+ },
491
+ {
492
+ name: "takumi",
493
+ description: "Rust-based high-performance renderer (recommended)",
494
+ bindings: {
495
+ "node": [
496
+ { name: TAKUMI_CORE_PACKAGE, installSpec: TAKUMI_CORE_INSTALL_SPEC, description: "Native Takumi renderer" }
497
+ ],
498
+ "wasm": [
499
+ { name: TAKUMI_WASM_PACKAGE, installSpec: TAKUMI_WASM_INSTALL_SPEC, description: "WASM Takumi renderer" }
500
+ ],
501
+ "wasm-fs": [
502
+ { name: TAKUMI_WASM_PACKAGE, installSpec: TAKUMI_WASM_INSTALL_SPEC, description: "WASM Takumi renderer" }
503
+ ]
504
+ }
505
+ },
506
+ {
507
+ name: "browser",
508
+ description: "Browser-based screenshot renderer",
509
+ bindings: {
510
+ node: [
511
+ { name: "playwright-core", description: "Headless browser automation" }
512
+ ]
513
+ }
514
+ }
515
+ ];
516
+ async function getInstalledProviders() {
517
+ const installed = [];
518
+ for (const provider of PROVIDER_DEPENDENCIES) {
519
+ if (provider.bindings.node) {
520
+ const allNodeInstalled = await Promise.all(
521
+ provider.bindings.node.map((dep) => hasResolvableDependency(dep.name))
522
+ );
523
+ if (allNodeInstalled.every(Boolean)) {
524
+ installed.push({ provider: provider.name, binding: "node" });
525
+ continue;
526
+ }
527
+ }
528
+ if (provider.bindings.wasm) {
529
+ const allWasmInstalled = await Promise.all(
530
+ provider.bindings.wasm.map((dep) => hasResolvableDependency(dep.name))
531
+ );
532
+ if (allWasmInstalled.every(Boolean)) {
533
+ installed.push({ provider: provider.name, binding: "wasm" });
534
+ }
535
+ }
536
+ }
537
+ return installed;
538
+ }
539
+ async function getMissingDependencies(provider, binding = "node") {
540
+ const missing = await getMissingProviderDependencyDefinitions(provider, binding);
541
+ return missing.map((d) => d.name);
542
+ }
543
+ async function getMissingProviderDependencyDefinitions(provider, binding = "node") {
544
+ const providerDef = PROVIDER_DEPENDENCIES.find((p) => p.name === provider);
545
+ if (!providerDef)
546
+ return [];
547
+ const deps = providerDef.bindings[binding] || providerDef.bindings.node || [];
548
+ const missing = [];
549
+ for (const dep of deps) {
550
+ if (!await hasResolvableDependency(dep.name))
551
+ missing.push(dep);
552
+ }
553
+ return missing;
554
+ }
555
+ function getDependencyDefinitions(provider, binding = "node") {
556
+ const providerDef = PROVIDER_DEPENDENCIES.find((p) => p.name === provider);
557
+ if (!providerDef)
558
+ return [];
559
+ return providerDef.bindings[binding] || providerDef.bindings.node || [];
560
+ }
561
+ function getDependencyInstallSpec(dep) {
562
+ return dep.installSpec || dep.name;
563
+ }
564
+ async function getMissingDependencyInstallSpecs(provider, binding = "node") {
565
+ const missing = await getMissingProviderDependencyDefinitions(provider, binding);
566
+ return missing.map(getDependencyInstallSpec);
567
+ }
568
+ function getProviderDependencyInstallSpecs(provider, binding = "node") {
569
+ return getDependencyDefinitions(provider, binding).map(getDependencyInstallSpec);
570
+ }
571
+ function getRecommendedBindingFromPreset(provider) {
572
+ const preset = resolveOgImagePreset();
573
+ const compatibility = getPresetNitroPresetCompatibility(preset);
574
+ return getRecommendedBinding(provider, compatibility);
575
+ }
576
+ function getRecommendedBinding(provider, compatibility) {
577
+ if (provider === "satori") {
578
+ const resvgBinding = compatibility.resvg;
579
+ if (resvgBinding === "wasm-fs")
580
+ return "wasm-fs";
581
+ if (resvgBinding === "wasm")
582
+ return "wasm";
583
+ return "node";
584
+ }
585
+ if (provider === "takumi") {
586
+ const takumiBinding = compatibility.takumi;
587
+ if (takumiBinding === "wasm")
588
+ return "wasm";
589
+ return "node";
590
+ }
591
+ if (provider === "browser") {
592
+ return "node";
593
+ }
594
+ return "node";
595
+ }
596
+ async function ensureProviderDependencies(provider, binding, nuxt) {
597
+ const missingDeps = await getMissingProviderDependencyDefinitions(provider, binding);
598
+ if (missingDeps.length === 0)
599
+ return { success: true, installed: [] };
600
+ const missingInstallSpecs = missingDeps.map(getDependencyInstallSpec);
601
+ const pm = await nypm.detectPackageManager(nuxt.options.rootDir);
602
+ const pmName = pm?.name || "npm";
603
+ logger_js.logger.info(`Installing ${provider} dependencies: ${missingInstallSpecs.join(", ")}`);
604
+ const installed = [];
605
+ for (const dep of missingDeps) {
606
+ const installSpec = getDependencyInstallSpec(dep);
607
+ const success = await nypm.addDependency(installSpec, {
608
+ cwd: nuxt.options.rootDir,
609
+ dev: false
610
+ }).then(() => {
611
+ installed.push(installSpec);
612
+ return true;
613
+ }).catch(() => {
614
+ logger_js.logger.error(`Failed to install ${installSpec}. Run manually: ${pmName} add ${installSpec}`);
615
+ return false;
616
+ });
617
+ if (!success)
618
+ return { success: false, installed };
619
+ }
620
+ return { success: true, installed };
621
+ }
622
+ function canPromptInteractively(env = {
623
+ hasTTY: stdEnv.hasTTY,
624
+ hasStdinTTY: Boolean(process.stdin?.isTTY),
625
+ isAgent: stdEnv.isAgent,
626
+ isCI: stdEnv.isCI
627
+ }) {
628
+ return env.hasTTY && env.hasStdinTTY && !env.isAgent && !env.isCI;
629
+ }
630
+ async function promptForRendererSelection() {
631
+ logger_js.logger.info("Welcome to Nuxt OG Image! No renderer dependencies detected.");
632
+ const renderer = await logger_js.logger.prompt("Which renderer would you like to use?", {
633
+ type: "select",
634
+ options: PROVIDER_DEPENDENCIES.map((p) => p.name),
635
+ initial: "takumi"
636
+ });
637
+ return renderer || "takumi";
638
+ }
639
+ async function validateProviderSetup(renderer, compatibility) {
640
+ const issues = [];
641
+ const binding = getRecommendedBinding(renderer, compatibility);
642
+ const missing = await getMissingDependencyInstallSpecs(renderer, binding);
643
+ if (missing.length > 0) {
644
+ issues.push(`Missing ${renderer} dependencies: ${missing.join(", ")}`);
645
+ }
646
+ if (renderer === "satori") {
647
+ const hasResvgNode = await hasResolvableDependency("@resvg/resvg-js");
648
+ const hasResvgWasm = await hasResolvableDependency("@resvg/resvg-wasm");
649
+ if (!hasResvgNode && !hasResvgWasm) {
650
+ issues.push("Satori requires either @resvg/resvg-js (node) or @resvg/resvg-wasm to render PNGs");
651
+ }
652
+ }
653
+ if (renderer === "browser") {
654
+ const hasPlaywright = await hasResolvableDependency("playwright-core");
655
+ if (!hasPlaywright && binding === "node") {
656
+ issues.push("Browser renderer requires playwright-core");
657
+ }
658
+ }
659
+ return { valid: issues.length === 0, issues };
660
+ }
661
+
662
+ exports.PROVIDER_DEPENDENCIES = PROVIDER_DEPENDENCIES;
663
+ exports.RE_LEGACY_SUFFIX = RE_LEGACY_SUFFIX;
664
+ exports.RE_RENDERER_SUFFIX = RE_RENDERER_SUFFIX;
665
+ exports.RE_WHITESPACE = RE_WHITESPACE;
666
+ exports.TAKUMI_CORE_INSTALL_SPEC = TAKUMI_CORE_INSTALL_SPEC;
667
+ exports.TAKUMI_WASM_INSTALL_SPEC = TAKUMI_WASM_INSTALL_SPEC;
668
+ exports.applyNitroPresetCompatibility = applyNitroPresetCompatibility;
669
+ exports.canPromptInteractively = canPromptInteractively;
670
+ exports.checkLocalChrome = checkLocalChrome;
671
+ exports.ensureDependencies = ensureDependencies;
672
+ exports.ensureProviderDependencies = ensureProviderDependencies;
673
+ exports.getInstalledProviders = getInstalledProviders;
674
+ exports.getMissingDependencies = getMissingDependencies;
675
+ exports.getMissingDependencyInstallSpecs = getMissingDependencyInstallSpecs;
676
+ exports.getPresetNitroPresetCompatibility = getPresetNitroPresetCompatibility;
677
+ exports.getProviderDependencyInstallSpecs = getProviderDependencyInstallSpecs;
678
+ exports.getRecommendedBinding = getRecommendedBinding;
679
+ exports.getRecommendedBindingFromPreset = getRecommendedBindingFromPreset;
680
+ exports.getRegisteredBaseNames = getRegisteredBaseNames;
681
+ exports.getRendererFromFilename = getRendererFromFilename;
682
+ exports.hasResolvableDependency = hasResolvableDependency;
683
+ exports.isUndefinedOrTruthy = isUndefinedOrTruthy;
684
+ exports.migrateDefaultsComponent = migrateDefaultsComponent;
685
+ exports.migrateFontsConfig = migrateFontsConfig;
686
+ exports.promptFontsMigration = promptFontsMigration;
687
+ exports.promptForRendererSelection = promptForRendererSelection;
688
+ exports.resolveOgImagePreset = resolveOgImagePreset;
689
+ exports.validateProviderSetup = validateProviderSetup;