nuxt-og-image 6.7.7 → 6.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import { pathToFileURL } from 'node:url';
3
3
  import { resolveModulePath } from 'exsolve';
4
4
  import { dirname, join } from 'pathe';
5
- import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.BMHzg1Vz.mjs';
5
+ import { e as extractVariantBaseClasses, r as resolveVariantPrefixes, h as resolveExtractedVars, l as resolveVarsDeep, s as simplifyCss, p as postProcessStyles, m as loadLightningCss, a as extractCssVars, c as extractUniversalVars, d as extractPerClassVars, g as extractVarsFromCss, f as extractClassStyles } from '../shared/nuxt-og-image.D936Fp6O.mjs';
6
6
  import 'node:crypto';
7
7
  import 'node:fs';
8
8
  import '@nuxt/kit';
@@ -12,6 +12,7 @@ import 'nuxt-site-config/kit';
12
12
  import 'nuxtseo-shared/kit';
13
13
  import 'pkg-types';
14
14
  import 'std-env';
15
+ import 'magic-string';
15
16
  import 'oxc-walker';
16
17
  import '../../dist/runtime/logger.js';
17
18
  import 'chrome-launcher';
@@ -20,7 +21,6 @@ import 'nuxtseo-shared/devtools';
20
21
  import 'unstorage';
21
22
  import 'unstorage/drivers/fs-lite';
22
23
  import '../../dist/runtime/server/og-image/utils/css.js';
23
- import 'magic-string';
24
24
  import 'strip-literal';
25
25
  import 'ufo';
26
26
  import 'unplugin';
@@ -1,8 +1,8 @@
1
1
  import { readFile } from 'node:fs/promises';
2
2
  import { defu } from 'defu';
3
3
  import { logger } from '../../dist/runtime/logger.js';
4
- import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.BMHzg1Vz.mjs';
5
- export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.BMHzg1Vz.mjs';
4
+ import { e as extractVariantBaseClasses, s as simplifyCss, a as extractCssVars, b as extractPropertyInitialValues, c as extractUniversalVars, d as extractPerClassVars, f as extractClassStyles, p as postProcessStyles, r as resolveVariantPrefixes, i as importOptionalPeer, g as extractVarsFromCss, h as resolveExtractedVars } from '../shared/nuxt-og-image.D936Fp6O.mjs';
5
+ export { j as importResolvedModule, k as resolveOptionalModulePath } from '../shared/nuxt-og-image.D936Fp6O.mjs';
6
6
  import 'node:crypto';
7
7
  import 'node:fs';
8
8
  import 'node:url';
@@ -13,14 +13,15 @@ import 'nuxtseo-shared/kit';
13
13
  import 'pathe';
14
14
  import 'pkg-types';
15
15
  import 'std-env';
16
+ import 'magic-string';
16
17
  import 'oxc-walker';
18
+ import 'exsolve';
17
19
  import 'chrome-launcher';
18
20
  import 'nypm';
19
21
  import 'nuxtseo-shared/devtools';
20
22
  import 'unstorage';
21
23
  import 'unstorage/drivers/fs-lite';
22
24
  import '../../dist/runtime/server/og-image/utils/css.js';
23
- import 'magic-string';
24
25
  import 'strip-literal';
25
26
  import 'ufo';
26
27
  import 'unplugin';
@@ -28,7 +29,6 @@ import '@vue/compiler-sfc';
28
29
  import 'ofetch';
29
30
  import 'ultrahtml';
30
31
  import '../../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js';
31
- import 'exsolve';
32
32
  import '../shared/nuxt-og-image.CJa2KCie.mjs';
33
33
  import 'magicast';
34
34
  import 'magicast/helpers';
package/dist/cli.mjs CHANGED
@@ -1211,7 +1211,7 @@ async function runCreate(name, args2) {
1211
1211
  message: "Add defineOgImage to a page? (relative path from pages/, leave empty to skip)",
1212
1212
  placeholder: "index.vue"
1213
1213
  });
1214
- if (!p.isCancel(pageInput) && pageInput && pageInput.trim()) {
1214
+ if (typeof pageInput === "string" && pageInput.trim()) {
1215
1215
  let pagePath = pageInput.trim();
1216
1216
  if (!pagePath.endsWith(".vue"))
1217
1217
  pagePath += ".vue";
@@ -1,6 +1,9 @@
1
+ import { fileURLToPath } from 'node:url'
1
2
  import { resolve } from 'pathe'
2
3
 
4
+ const currentDir = fileURLToPath(new URL('.', import.meta.url))
5
+
3
6
  // Nuxt SEO devtools panel, shipped as a layer (Model C). Components flat-registered.
4
7
  export default defineNuxtConfig({
5
- components: [{ path: resolve(__dirname, './components'), pathPrefix: false }],
8
+ components: [{ path: resolve(currentDir, './components'), pathPrefix: false }],
6
9
  })
package/dist/module.d.mts CHANGED
@@ -118,6 +118,16 @@ interface ModuleOptions {
118
118
  * @default 60 * 60 * 24 * 3 (3 days)
119
119
  */
120
120
  cacheMaxAgeSeconds?: number;
121
+ /**
122
+ * Include Twitter card meta tags (`twitter:card`, `twitter:image`, etc)
123
+ * alongside the Open Graph image tags.
124
+ *
125
+ * Set to `false` to only emit `og:image` tags. When unset, the module respects
126
+ * `seo.automaticTwitterTags` (nuxt-seo-kit) before defaulting to `true`.
127
+ *
128
+ * @default true
129
+ */
130
+ includeTwitter?: boolean;
121
131
  /**
122
132
  * Font subsets to download when resolving missing font families via fontless.
123
133
  *
package/dist/module.d.ts CHANGED
@@ -118,6 +118,16 @@ interface ModuleOptions {
118
118
  * @default 60 * 60 * 24 * 3 (3 days)
119
119
  */
120
120
  cacheMaxAgeSeconds?: number;
121
+ /**
122
+ * Include Twitter card meta tags (`twitter:card`, `twitter:image`, etc)
123
+ * alongside the Open Graph image tags.
124
+ *
125
+ * Set to `false` to only emit `og:image` tags. When unset, the module respects
126
+ * `seo.automaticTwitterTags` (nuxt-seo-kit) before defaulting to `true`.
127
+ *
128
+ * @default true
129
+ */
130
+ includeTwitter?: boolean;
121
131
  /**
122
132
  * Font subsets to download when resolving missing font families via fontless.
123
133
  *
package/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "nuxt": ">=3.16.0"
5
5
  },
6
6
  "configKey": "ogImage",
7
- "version": "6.7.7",
7
+ "version": "6.8.0",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
package/dist/module.mjs CHANGED
@@ -10,16 +10,17 @@ import 'nuxtseo-shared/kit';
10
10
  import 'pathe';
11
11
  import 'pkg-types';
12
12
  import 'std-env';
13
- export { n as default } from './shared/nuxt-og-image.BMHzg1Vz.mjs';
13
+ export { n as default } from './shared/nuxt-og-image.D936Fp6O.mjs';
14
14
  import '../dist/runtime/logger.js';
15
+ import 'magic-string';
15
16
  import 'oxc-walker';
17
+ import 'exsolve';
16
18
  import 'chrome-launcher';
17
19
  import 'nypm';
18
20
  import 'nuxtseo-shared/devtools';
19
21
  import 'unstorage';
20
22
  import 'unstorage/drivers/fs-lite';
21
23
  import '../dist/runtime/server/og-image/utils/css.js';
22
- import 'magic-string';
23
24
  import 'strip-literal';
24
25
  import 'ufo';
25
26
  import 'unplugin';
@@ -27,7 +28,6 @@ import '@vue/compiler-sfc';
27
28
  import 'ofetch';
28
29
  import 'ultrahtml';
29
30
  import '../dist/runtime/server/og-image/core/transforms/emojis/emoji-utils.js';
30
- import 'exsolve';
31
31
  import './shared/nuxt-og-image.CJa2KCie.mjs';
32
32
  import 'magicast';
33
33
  import 'magicast/helpers';
@@ -63,6 +63,7 @@ export function clientProcessOgImageOptions(input, route, basePath) {
63
63
  const baseURL = rc.app.baseURL;
64
64
  const publicCfg = rc.public?.["nuxt-og-image"] || {};
65
65
  const defaults = publicCfg.defaults || {};
66
+ const metaOptions = { includeTwitter: publicCfg.includeTwitter };
66
67
  const paths = [];
67
68
  for (const rawInput of inputs) {
68
69
  const resolved = resolveReactiveOptions(rawInput);
@@ -78,13 +79,13 @@ export function clientProcessOgImageOptions(input, route, basePath) {
78
79
  const ogKey = validOptions.key || "og";
79
80
  if (validOptions.url) {
80
81
  const url = validOptions.url;
81
- registerClientOgHead(ogKey, { meta: generateMeta(url, validOptions) }, { tagPriority: "high" });
82
+ registerClientOgHead(ogKey, { meta: generateMeta(url, validOptions, metaOptions) }, { tagPriority: "high" });
82
83
  paths.push(url);
83
84
  continue;
84
85
  }
85
86
  if (publicCfg.hasServerRuntime) {
86
87
  const finalUrl2 = buildResolverUrl(baseURL, basePath, ogKey, route.query);
87
- registerClientOgHead(ogKey, { meta: generateMeta(finalUrl2, validOptions) }, { tagPriority: 35 });
88
+ registerClientOgHead(ogKey, { meta: generateMeta(finalUrl2, validOptions, metaOptions) }, { tagPriority: 35 });
88
89
  paths.push(finalUrl2);
89
90
  continue;
90
91
  }
@@ -98,7 +99,7 @@ export function clientProcessOgImageOptions(input, route, basePath) {
98
99
  const result = buildOgImageUrl(urlOpts, extension, true, defaults, void 0);
99
100
  const resolvedUrl = joinURL("/", baseURL, result.url);
100
101
  const finalUrl = opts._query && Object.keys(opts._query).length ? withQuery(resolvedUrl, { _query: opts._query }) : resolvedUrl;
101
- registerClientOgHead(ogKey, { meta: generateMeta(finalUrl, opts) }, { processTemplateParams: true, tagPriority: 35 });
102
+ registerClientOgHead(ogKey, { meta: generateMeta(finalUrl, opts, metaOptions) }, { processTemplateParams: true, tagPriority: 35 });
102
103
  paths.push(finalUrl);
103
104
  }
104
105
  return paths;
@@ -53,7 +53,8 @@ export function setHeadOgImagePrebuilt(input) {
53
53
  const url = input.url;
54
54
  if (!url)
55
55
  return;
56
- const meta = generateMeta(url, input);
56
+ const { includeTwitter } = useOgImageRuntimeConfig();
57
+ const meta = generateMeta(url, input, { includeTwitter });
57
58
  useHead({ meta }, { tagPriority: "high" });
58
59
  }
59
60
  export function createOgImageMeta(src, input, ssrContext, pagePath, head) {
@@ -117,7 +118,7 @@ export function createOgImageMeta(src, input, ssrContext, pagePath, head) {
117
118
  prerenderPaths.set(ogKey, (finalUrl.split("?")[0] || finalUrl).replace(/,/g, "%2C"));
118
119
  }
119
120
  }
120
- return generateMeta(finalUrl, opts);
121
+ return generateMeta(finalUrl, opts, { includeTwitter: ogImageConfig.includeTwitter });
121
122
  });
122
123
  }
123
124
  }, {
@@ -0,0 +1,4 @@
1
+ declare const _default: {
2
+ readonly then: <TResult1 = unknown, TResult2 = never>(onfulfilled?: ((value: unknown) => TResult1 | PromiseLike<TResult1>) | null | undefined, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null | undefined) => Promise<TResult1 | TResult2>;
3
+ };
4
+ export default _default;
@@ -0,0 +1,33 @@
1
+ import wrapHarfBuzz from "#og-image/harfbuzz-adapter";
2
+ import callbackModules from "#og-image/harfbuzz-callbacks";
3
+ import createHarfBuzz from "#og-image/harfbuzz-factory";
4
+ function loadHarfBuzz() {
5
+ return import("#og-image/harfbuzz-wasm").then(async ({ default: importedWasm }) => {
6
+ const wasm = await importedWasm;
7
+ const callbacks = callbackModules && Object.fromEntries(await Promise.all(
8
+ Object.entries(callbackModules).map(async ([signature, module]) => [signature, await module])
9
+ ));
10
+ return new Promise((resolve, reject) => {
11
+ createHarfBuzz({
12
+ ...callbacks && {
13
+ convertJsFunctionToWasm(callback, signature) {
14
+ return new WebAssembly.Instance(callbacks[signature], { e: { f: callback } }).exports.f;
15
+ }
16
+ },
17
+ instantiateWasm(imports, receiveInstance) {
18
+ Promise.resolve().then(() => typeof wasm === "function" ? wasm(imports) : new WebAssembly.Instance(wasm, imports)).then((result) => {
19
+ receiveInstance("instance" in result ? result.instance : result);
20
+ }).catch(reject);
21
+ return {};
22
+ }
23
+ }).then(wrapHarfBuzz).then(resolve, reject);
24
+ });
25
+ });
26
+ }
27
+ let runtime;
28
+ export default {
29
+ get then() {
30
+ const promise = runtime ||= loadHarfBuzz();
31
+ return promise.then.bind(promise);
32
+ }
33
+ };
@@ -1,20 +1,12 @@
1
1
  import type { RuntimeFontConfig } from '../../types.js';
2
2
  /**
3
- * Rename unicode-range subset fonts so renderers can fall back between them.
4
- *
5
- * Both Satori and Takumi pick the first loaded font file for a given family
6
- * name and don't fall back to other font files for missing glyphs. When CJK
7
- * fonts like "Noto Sans SC" are split into 100+ unicode-range subsets by
8
- * fontsource, each covering ~200 characters, this means only one subset's
9
- * glyphs render while the rest show as .notdef boxes.
10
- *
11
- * Fix: rename each subset to "Family__N" and use font-family fallback chains
12
- * so renderers try each subset in order per character.
3
+ * Give subsets stable names so renderers can fall back between their glyphs.
4
+ * Renderers cache registrations by name across renders with different filtered subsets.
13
5
  */
14
6
  export declare function renameSubsetFonts(fonts: RuntimeFontConfig[]): RuntimeFontConfig[];
15
7
  /**
16
8
  * Build a mapping from original family names to their renamed subset chain.
17
- * E.g., "Noto Sans SC" → ["Noto Sans SC__0", "Noto Sans SC__1", ...]
9
+ * E.g., "Noto Sans SC" → ["Noto Sans SC__<hash-a>", "Noto Sans SC__<hash-b>", ...]
18
10
  */
19
11
  export declare function buildSubsetFamilyChain(fonts: RuntimeFontConfig[]): Map<string, string[]>;
20
12
  /**
@@ -1,44 +1,67 @@
1
+ import { fnv1a64Base36 } from "fnv1a-64";
1
2
  export function renameSubsetFonts(fonts) {
2
3
  const groups = /* @__PURE__ */ new Map();
3
- for (const f of fonts) {
4
- const key = `${f.family}\0${f.weight}\0${f.style}`;
5
- const arr = groups.get(key);
6
- if (arr)
7
- arr.push(f);
4
+ for (const font of fonts) {
5
+ const members = groups.get(font.family);
6
+ if (members)
7
+ members.push(font);
8
8
  else
9
- groups.set(key, [f]);
9
+ groups.set(font.family, [font]);
10
10
  }
11
- const result = [];
12
- let changed = false;
11
+ const aliases = /* @__PURE__ */ new Map();
13
12
  for (const members of groups.values()) {
14
- const needsRename = members.length > 1 && new Set(members.map((f) => f.cacheKey)).size > 1;
15
- if (!needsRename) {
16
- result.push(...members);
17
- continue;
13
+ const faces = /* @__PURE__ */ new Map();
14
+ for (const font of members) {
15
+ const face = `${font.weight}\0${font.style}`;
16
+ const subsets = faces.get(face);
17
+ if (subsets)
18
+ subsets.push(font);
19
+ else
20
+ faces.set(face, [font]);
18
21
  }
19
- changed = true;
20
- for (let i = 0; i < members.length; i++) {
21
- const f = members[i];
22
- result.push({
23
- ...f,
24
- originalFamily: f.originalFamily || f.family,
25
- family: `${f.family}__${i}`
22
+ const needsRename = members.some((font) => font.unicodeRange) || [...faces.values()].some((subsets) => new Set(subsets.map((font) => font.cacheKey)).size > 1);
23
+ if (!needsRename)
24
+ continue;
25
+ const slots = [];
26
+ for (const subsets of faces.values()) {
27
+ const binaries = /* @__PURE__ */ new Map();
28
+ for (const font of subsets) {
29
+ const source = font.src || font.localPath || font.cacheKey;
30
+ const entries = binaries.get(source);
31
+ if (entries)
32
+ entries.push(font);
33
+ else
34
+ binaries.set(source, [font]);
35
+ }
36
+ const ordered = [...binaries.values()].sort((a, b) => {
37
+ const left = `${a[0].unicodeRange || ""}\0${a[0].src || a[0].localPath || a[0].cacheKey}`;
38
+ const right = `${b[0].unicodeRange || ""}\0${b[0].src || b[0].localPath || b[0].cacheKey}`;
39
+ return left < right ? -1 : left > right ? 1 : 0;
26
40
  });
41
+ for (const [index, entries] of ordered.entries())
42
+ (slots[index] ||= []).push(...entries);
43
+ }
44
+ for (const slot of slots) {
45
+ const identities = [...new Set(slot.map((font) => JSON.stringify([
46
+ font.weight,
47
+ font.style,
48
+ font.src || font.localPath || font.cacheKey
49
+ ])))].sort();
50
+ const alias = `${slot[0].family}__${fnv1a64Base36(JSON.stringify(identities))}`;
51
+ for (const font of slot)
52
+ aliases.set(font, alias);
27
53
  }
28
54
  }
29
- return changed ? result : fonts;
55
+ return aliases.size ? fonts.map((font) => aliases.has(font) ? { ...font, originalFamily: font.originalFamily || font.family, family: aliases.get(font) } : font) : fonts;
30
56
  }
31
57
  export function buildSubsetFamilyChain(fonts) {
32
58
  const chains = /* @__PURE__ */ new Map();
33
59
  for (const f of fonts) {
34
- if (!f.originalFamily)
35
- continue;
36
- const arr = chains.get(f.originalFamily);
37
- if (arr)
38
- arr.push(f.family);
39
- else
40
- chains.set(f.originalFamily, [f.family]);
60
+ if (f.originalFamily)
61
+ chains.set(f.originalFamily, []);
41
62
  }
63
+ for (const f of fonts)
64
+ chains.get(f.originalFamily || f.family)?.push(f.family);
42
65
  return chains;
43
66
  }
44
67
  export function resolveSubsetChain(family, chains) {
@@ -51,7 +51,7 @@ export interface LoadFontsForRendererOptions extends LoadFontsOptions {
51
51
  *
52
52
  * When a font family has multiple unicode-range subsets loaded (e.g., CJK fonts
53
53
  * split into many small chunks), renames each subset to a unique family name
54
- * (e.g., "Noto Sans SC__0", "Noto Sans SC__1") so renderers use font-family
54
+ * (e.g., "Noto Sans SC__<stable hash per binary>") so renderers use font-family
55
55
  * fallback chains for per-character glyph coverage. Without this, both Satori
56
56
  * and Takumi pick the first font file and show .notdef for characters in other subsets.
57
57
  */
@@ -1,4 +1,4 @@
1
- import { createError, getRequestHost, H3Error, setHeader } from "#nuxtseo/h3";
1
+ import { appendResponseHeader, createError, getRequestHost, H3Error, setHeader } from "#nuxtseo/h3";
2
2
  import { getSiteConfig } from "#site-config/server/composables/getSiteConfig";
3
3
  import { logger } from "../../logger.js";
4
4
  import { getBuildCachedImage, setBuildCachedImage } from "../og-image/cache/buildCache.js";
@@ -22,8 +22,8 @@ export async function imageEventHandler(e) {
22
22
  } finally {
23
23
  timings.record("total", performance.now() - reqStart);
24
24
  const header = timings.header();
25
- if (header)
26
- setHeader(e, "Server-Timing", header);
25
+ if (header && !e.node?.res?.headersSent)
26
+ appendResponseHeader(e, "Server-Timing", header);
27
27
  }
28
28
  }
29
29
  async function renderOgImage(e, ctx) {
@@ -18,6 +18,7 @@ const RE_HASH_SEGMENT = /^o_([a-z0-9]+)$/i;
18
18
  const RE_COMMA_PARAM_SEPARATOR = /,(?=\w+_)/;
19
19
  const RE_SIGNATURE_SUFFIX = /,s_[^,]+$/;
20
20
  const RE_NON_ASCII = /[^\u0000-\u007F]/;
21
+ const RE_WINDOWS_RESERVED_FILENAME_CHARACTERS = /[<>:"/\\|?*\u0000-\u001F]/;
21
22
  const PARAM_ALIASES = {
22
23
  w: "width",
23
24
  h: "height",
@@ -149,7 +150,7 @@ export function encodeOgImageParams(options, defaults) {
149
150
  } else {
150
151
  const escaped = str.startsWith("~") ? `~${str}` : str;
151
152
  const encoded = encodeURIComponent(escaped.replace(RE_UNDERSCORE, "__")).replace(RE_PERCENT20, "+");
152
- if (encoded.includes("%")) {
153
+ if (encoded.includes("%") || RE_WINDOWS_RESERVED_FILENAME_CHARACTERS.test(str)) {
153
154
  parts.push(`${alias}_~${b64Encode(str)}`);
154
155
  } else {
155
156
  parts.push(`${alias}_${encoded}`);
@@ -2,7 +2,16 @@ import type { ResolvableMeta } from '@unhead/vue';
2
2
  import type { OgImageOptions, OgImagePrebuilt } from './types.js';
3
3
  export { extractSocialPreviewTags, toBase64Image } from './pure.js';
4
4
  export { buildOgImageUrl, decodeOgImageParams, encodeOgImageParams, extractEncodedSegment, hashOgImageOptions, parseOgImageUrl, signEncodedParams, verifyOgImageSignature } from './shared/urlEncoding.js';
5
- export declare function generateMeta(url: OgImagePrebuilt['url'] | string, resolvedOptions: OgImageOptions | OgImagePrebuilt): ResolvableMeta[];
5
+ export interface GenerateMetaOptions {
6
+ /**
7
+ * Emit Twitter card meta tags (`twitter:card`, `twitter:image`, etc) alongside
8
+ * the Open Graph image tags.
9
+ *
10
+ * @default true
11
+ */
12
+ includeTwitter?: boolean;
13
+ }
14
+ export declare function generateMeta(url: OgImagePrebuilt['url'] | string, resolvedOptions: OgImageOptions | OgImagePrebuilt, metaOptions?: GenerateMetaOptions): ResolvableMeta[];
6
15
  export declare function isInternalRoute(path: string): boolean;
7
16
  export declare function separateProps(options: OgImageOptions | undefined, ignoreKeys?: string[]): OgImageOptions;
8
17
  /**
@@ -3,10 +3,10 @@ import { toValue } from "vue";
3
3
  export { extractSocialPreviewTags, toBase64Image } from "./pure.js";
4
4
  export { buildOgImageUrl, decodeOgImageParams, encodeOgImageParams, extractEncodedSegment, hashOgImageOptions, parseOgImageUrl, signEncodedParams, verifyOgImageSignature } from "./shared/urlEncoding.js";
5
5
  const RE_KEBAB_CASE = /-([a-z])/g;
6
- export function generateMeta(url, resolvedOptions) {
6
+ export function generateMeta(url, resolvedOptions, metaOptions = {}) {
7
7
  const key = resolvedOptions.key || "og";
8
8
  const isTwitterOnly = key === "twitter";
9
- const includeTwitter = key === "og" || key === "twitter";
9
+ const includeTwitter = (key === "og" || key === "twitter") && metaOptions.includeTwitter !== false;
10
10
  const meta = [];
11
11
  if (includeTwitter) {
12
12
  meta.push({ name: "twitter:card", content: "summary_large_image" });
@@ -37,6 +37,8 @@ export interface OgImageRuntimeConfig {
37
37
  publicStoragePath: string;
38
38
  defaults: Omit<OgImageOptions, 'component' | 'renderer' | 'props' | 'url' | 'html' | 'key' | 'cacheKey' | '_query' | '_hash' | 'socialPreview'>;
39
39
  debug: boolean;
40
+ /** Whether twitter:* meta tags are emitted alongside og:image tags */
41
+ includeTwitter: boolean;
40
42
  baseCacheKey: string;
41
43
  hasNuxtIcon: boolean;
42
44
  hasNuxtContent?: boolean;