nuxt-og-image 6.7.6 → 6.7.7
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/LICENSE.md +6 -0
- package/dist/chunks/decode.mjs +1087 -0
- package/dist/chunks/tw4.mjs +1 -1
- package/dist/chunks/uno.mjs +2 -2
- package/dist/module.d.ts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/server/og-image/bindings/font-assets/dev-prerender.js +5 -6
- package/dist/runtime/server/og-image/bindings/font-assets/node.d.ts +1 -1
- package/dist/runtime/server/og-image/bindings/font-assets/node.js +2 -3
- package/dist/runtime/server/og-image/browser/screenshot.js +2 -1
- package/dist/runtime/server/og-image/font-source.d.ts +3 -12
- package/dist/runtime/server/og-image/font-source.js +1 -4
- package/dist/runtime/server/og-image/fonts.d.ts +0 -7
- package/dist/runtime/server/og-image/fonts.js +1 -1
- package/dist/runtime/server/og-image/takumi/renderer.js +2 -2
- package/dist/runtime/server/routes/resolve.js +2 -1
- package/dist/runtime/server/util/fetchLocalAsset.d.ts +1 -1
- package/dist/runtime/server/util/fetchLocalAsset.js +2 -1
- package/dist/runtime/types.d.ts +1 -1
- package/dist/shared/{nuxt-og-image.BIbfn5xQ.mjs → nuxt-og-image.BMHzg1Vz.mjs} +227 -76
- package/package.json +21 -22
- package/dist/chunks/tw4.cjs +0 -493
- package/dist/chunks/uno.cjs +0 -249
- package/dist/cli.cjs +0 -1345
- package/dist/cli.d.cts +0 -1
- package/dist/content.cjs +0 -21
- package/dist/content.d.cts +0 -16
- package/dist/module.cjs +0 -39
- package/dist/module.d.cts +0 -219
- package/dist/shared/nuxt-og-image.CMYbz66P.cjs +0 -193
- package/dist/shared/nuxt-og-image.CdpC3njh.cjs +0 -5898
|
@@ -2,7 +2,7 @@ import { createHash, randomBytes } from 'node:crypto';
|
|
|
2
2
|
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
|
-
import { pathToFileURL } from 'node:url';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
6
|
import { resolvePath, useNuxt, addTemplate, updateTemplates, directoryToURL, tryResolveModule, addTypeTemplate, defineNuxtModule, createResolver, addImports, hasNuxtModule, hasNuxtModuleCompatibility, getNuxtModuleVersion, addVitePlugin, addBuildPlugin, addServerHandler, addPlugin, addComponentsDir, getNuxtVersion, addServerPlugin } from '@nuxt/kit';
|
|
7
7
|
import { defu } from 'defu';
|
|
8
8
|
import { fnv1a64Base36 } from 'fnv1a-64';
|
|
@@ -868,18 +868,18 @@ ${content}`, "utf-8");
|
|
|
868
868
|
if (isCssChange || normalizedPath.includes("uno.config")) {
|
|
869
869
|
clearTimeout(cssRefreshTimer);
|
|
870
870
|
cssRefreshTimer = setTimeout(() => {
|
|
871
|
-
safeBroadcast(() => rpc.broadcast.refresh());
|
|
871
|
+
safeBroadcast(() => rpc.broadcast.refresh.asEvent());
|
|
872
872
|
}, 200);
|
|
873
873
|
return;
|
|
874
874
|
}
|
|
875
875
|
if ((e === "change" || e.includes("link")) && (normalizedPath.startsWith("pages") || normalizedPath.startsWith("content"))) {
|
|
876
|
-
safeBroadcast(() => rpc.broadcast.refreshRouteData(normalizedPath));
|
|
876
|
+
safeBroadcast(() => rpc.broadcast.refreshRouteData.asEvent(normalizedPath));
|
|
877
877
|
}
|
|
878
878
|
if (options.componentDirs.some((dir) => normalizedPath.includes(dir))) {
|
|
879
879
|
if (e === "change")
|
|
880
|
-
safeBroadcast(() => rpc.broadcast.refresh());
|
|
880
|
+
safeBroadcast(() => rpc.broadcast.refresh.asEvent());
|
|
881
881
|
else
|
|
882
|
-
safeBroadcast(() => rpc.broadcast.refreshGlobalData());
|
|
882
|
+
safeBroadcast(() => rpc.broadcast.refreshGlobalData.asEvent());
|
|
883
883
|
}
|
|
884
884
|
});
|
|
885
885
|
});
|
|
@@ -1700,6 +1700,9 @@ const RE_FONT_EXT = /\.(?:woff2?|ttf|otf)$/i;
|
|
|
1700
1700
|
const RE_NON_WORD = /\W+/g;
|
|
1701
1701
|
const FONTS_URL_PREFIX = "/_fonts";
|
|
1702
1702
|
const STATIC_FONTS_PREFIX = "/_og-static-fonts";
|
|
1703
|
+
function getStaticFontCacheDir(buildDir) {
|
|
1704
|
+
return join(buildDir, "cache", "og-image", "static-fonts");
|
|
1705
|
+
}
|
|
1703
1706
|
function fontKey(f) {
|
|
1704
1707
|
return `${f.family}-${f.weight}-${f.style}-${f.unicodeRange || "default"}`;
|
|
1705
1708
|
}
|
|
@@ -1883,7 +1886,9 @@ function parseConfiguredLocalFonts(nuxt) {
|
|
|
1883
1886
|
}
|
|
1884
1887
|
async function parseFontsFromTemplate(nuxt, options) {
|
|
1885
1888
|
const weightsKey = options.requiredWeights?.toSorted((a, b) => a - b).join(",") || "";
|
|
1886
|
-
const
|
|
1889
|
+
const sourceKey = [...options.fontState.sourceMap.entries()].toSorted(([a], [b]) => a.localeCompare(b)).flat().join(",");
|
|
1890
|
+
const fallbackKey = [...options.fontState.fallbackMap.entries()].toSorted(([a], [b]) => a.localeCompare(b)).flat().join(",");
|
|
1891
|
+
const cacheKey = `${sourceKey}:${fallbackKey}:${weightsKey}`;
|
|
1887
1892
|
const cache = nuxt._ogImageParsedFontsCache ||= /* @__PURE__ */ new Map();
|
|
1888
1893
|
const cached = cache.get(cacheKey);
|
|
1889
1894
|
if (cached)
|
|
@@ -1904,11 +1909,11 @@ async function parseFontsFromTemplate(nuxt, options) {
|
|
|
1904
1909
|
}
|
|
1905
1910
|
}
|
|
1906
1911
|
const fontlessFamilies = new Set(
|
|
1907
|
-
[...fontMap.values()].filter((f) => options.
|
|
1912
|
+
[...fontMap.values()].filter((f) => options.fontState.fallbackMap.has(`${f.family}-${f.weight}-${f.style}`)).map((f) => f.family)
|
|
1908
1913
|
);
|
|
1909
1914
|
const expandedFonts = [...fontMap.values()];
|
|
1910
1915
|
const existingKeys = new Set(expandedFonts.map((f) => `${f.family}-${f.weight}-${f.style}`));
|
|
1911
|
-
for (const [key] of options.
|
|
1916
|
+
for (const [key] of options.fontState.fallbackMap) {
|
|
1912
1917
|
if (existingKeys.has(key))
|
|
1913
1918
|
continue;
|
|
1914
1919
|
const match = key.match(RE_FONT_KEY);
|
|
@@ -1942,18 +1947,18 @@ async function parseFontsFromTemplate(nuxt, options) {
|
|
|
1942
1947
|
}
|
|
1943
1948
|
}
|
|
1944
1949
|
}
|
|
1945
|
-
const defaultUnicodeRange = "U+0-FF, U+131, U+152-153, U+2BB-2BC, U+2C6, U+2DA, U+2DC, U+304, U+308, U+329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD";
|
|
1946
1950
|
const result = expandedFonts.map((font) => {
|
|
1947
1951
|
const fKey = `${font.family}-${font.weight}-${font.style}`;
|
|
1948
|
-
const
|
|
1949
|
-
const
|
|
1952
|
+
const convertedSource = options.fontState.sourceMap.get(font.src);
|
|
1953
|
+
const fontlessPath = options.fontState.fallbackMap.get(fKey);
|
|
1954
|
+
const satoriSrc = convertedSource || fontlessPath || (font.isWoff2 ? void 0 : fontlessFamilies.has(font.family) ? void 0 : font.src);
|
|
1950
1955
|
return {
|
|
1951
1956
|
family: font.family,
|
|
1952
1957
|
src: font.src,
|
|
1953
1958
|
weight: font.weight,
|
|
1954
1959
|
style: font.style,
|
|
1955
1960
|
satoriSrc,
|
|
1956
|
-
unicodeRange: font.unicodeRange
|
|
1961
|
+
unicodeRange: font.unicodeRange,
|
|
1957
1962
|
subset: font.subset,
|
|
1958
1963
|
weightRange: font.weightRange
|
|
1959
1964
|
};
|
|
@@ -2005,10 +2010,10 @@ function copyStaticFontsToOutput(options) {
|
|
|
2005
2010
|
count++;
|
|
2006
2011
|
}
|
|
2007
2012
|
}
|
|
2008
|
-
const
|
|
2009
|
-
if (existsSync(
|
|
2010
|
-
for (const file of fs.readdirSync(
|
|
2011
|
-
fs.copyFileSync(join(
|
|
2013
|
+
const staticFontDir = getStaticFontCacheDir(buildDir);
|
|
2014
|
+
if (existsSync(staticFontDir)) {
|
|
2015
|
+
for (const file of fs.readdirSync(staticFontDir).filter((f) => f.endsWith(".ttf") || f.endsWith(".woff"))) {
|
|
2016
|
+
fs.copyFileSync(join(staticFontDir, file), join(outputDir, file));
|
|
2012
2017
|
count++;
|
|
2013
2018
|
}
|
|
2014
2019
|
}
|
|
@@ -2028,6 +2033,20 @@ function isConfiguredLocalFontFamily(nuxt, family) {
|
|
|
2028
2033
|
const config = getNuxtFontsFamilyConfig(nuxt, family);
|
|
2029
2034
|
return !!config && config.global === true && (config.provider === "local" || typeof config.src === "string");
|
|
2030
2035
|
}
|
|
2036
|
+
function getFamilyRequirements(fontRequirements, family) {
|
|
2037
|
+
const familyName = family.toLowerCase();
|
|
2038
|
+
const matchingComponents = Object.values(fontRequirements.componentMap).filter((component) => component.families.some((candidate) => candidate.toLowerCase() === familyName));
|
|
2039
|
+
if (matchingComponents.length === 0 || matchingComponents.some((component) => component.hasDynamicBindings)) {
|
|
2040
|
+
return {
|
|
2041
|
+
weights: fontRequirements.weights,
|
|
2042
|
+
styles: fontRequirements.styles
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
return {
|
|
2046
|
+
weights: [...new Set(matchingComponents.flatMap((component) => component.weights))],
|
|
2047
|
+
styles: [...new Set(matchingComponents.flatMap((component) => component.styles))]
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2031
2050
|
async function initFontless(options) {
|
|
2032
2051
|
if (getFontlessContext(options.nuxt))
|
|
2033
2052
|
return;
|
|
@@ -2085,7 +2104,7 @@ async function initFontless(options) {
|
|
|
2085
2104
|
weights: [400, 700],
|
|
2086
2105
|
styles: ["normal", "italic"],
|
|
2087
2106
|
subsets: options.fontSubsets || ["latin"],
|
|
2088
|
-
//
|
|
2107
|
+
// Static fallbacks must use formats supported by both renderers.
|
|
2089
2108
|
formats: ["woff", "ttf"]
|
|
2090
2109
|
}
|
|
2091
2110
|
},
|
|
@@ -2127,8 +2146,8 @@ function resolveWeightsFromFontEntry(fontWeight, requestedWeights) {
|
|
|
2127
2146
|
async function downloadStaticFonts(options) {
|
|
2128
2147
|
if (options.families.length === 0)
|
|
2129
2148
|
return [];
|
|
2130
|
-
const
|
|
2131
|
-
fs.mkdirSync(
|
|
2149
|
+
const staticFontDir = getStaticFontCacheDir(options.nuxt.options.buildDir);
|
|
2150
|
+
fs.mkdirSync(staticFontDir, { recursive: true });
|
|
2132
2151
|
await initFontless({ nuxt: options.nuxt, logger: options.logger, fontSubsets: options.fontSubsets });
|
|
2133
2152
|
const fontlessCtx = getFontlessContext(options.nuxt);
|
|
2134
2153
|
if (!fontlessCtx) {
|
|
@@ -2143,7 +2162,7 @@ async function downloadStaticFonts(options) {
|
|
|
2143
2162
|
family,
|
|
2144
2163
|
weights,
|
|
2145
2164
|
styles,
|
|
2146
|
-
|
|
2165
|
+
staticFontDir,
|
|
2147
2166
|
resolver: fontlessCtx.resolver,
|
|
2148
2167
|
logger: options.logger
|
|
2149
2168
|
});
|
|
@@ -2155,7 +2174,7 @@ async function downloadStaticFonts(options) {
|
|
|
2155
2174
|
if (hasVariableBinary && weights.length > 1) {
|
|
2156
2175
|
options.logger.debug(`${family}: provider returned variable font binary, retrying with alternative providers`);
|
|
2157
2176
|
for (const r of familyResults) {
|
|
2158
|
-
const filePath = join(
|
|
2177
|
+
const filePath = join(staticFontDir, r.filename);
|
|
2159
2178
|
if (fs.existsSync(filePath))
|
|
2160
2179
|
fs.unlinkSync(filePath);
|
|
2161
2180
|
}
|
|
@@ -2165,7 +2184,7 @@ async function downloadStaticFonts(options) {
|
|
|
2165
2184
|
family,
|
|
2166
2185
|
weights,
|
|
2167
2186
|
styles,
|
|
2168
|
-
|
|
2187
|
+
staticFontDir,
|
|
2169
2188
|
logger: options.logger,
|
|
2170
2189
|
resolver: fontlessCtx.resolver,
|
|
2171
2190
|
provider
|
|
@@ -2176,7 +2195,7 @@ async function downloadStaticFonts(options) {
|
|
|
2176
2195
|
for (const r of altResults) altUrls.set(r.url, [...altUrls.get(r.url) || [], r.weight]);
|
|
2177
2196
|
if ([...altUrls.values()].some((ws) => ws.length > 1)) {
|
|
2178
2197
|
for (const r of altResults) {
|
|
2179
|
-
const filePath = join(
|
|
2198
|
+
const filePath = join(staticFontDir, r.filename);
|
|
2180
2199
|
if (fs.existsSync(filePath))
|
|
2181
2200
|
fs.unlinkSync(filePath);
|
|
2182
2201
|
}
|
|
@@ -2255,7 +2274,7 @@ async function listLocalPublicFontFiles(nuxt) {
|
|
|
2255
2274
|
return out;
|
|
2256
2275
|
}
|
|
2257
2276
|
async function resolveAndDownloadFamily(options) {
|
|
2258
|
-
const { family, weights, styles,
|
|
2277
|
+
const { family, weights, styles, staticFontDir, logger } = options;
|
|
2259
2278
|
const results = [];
|
|
2260
2279
|
try {
|
|
2261
2280
|
const override = options.provider ? { name: family, weights, styles, provider: options.provider } : { name: family, weights, styles };
|
|
@@ -2281,7 +2300,7 @@ async function resolveAndDownloadFamily(options) {
|
|
|
2281
2300
|
const ext = isTtf ? "ttf" : "woff";
|
|
2282
2301
|
for (const weight of resolvedWeights) {
|
|
2283
2302
|
const filename = `${family.replace(RE_NON_ALPHANUMERIC, "_")}-${weight}-${style}.${ext}`;
|
|
2284
|
-
const destPath = join(
|
|
2303
|
+
const destPath = join(staticFontDir, filename);
|
|
2285
2304
|
if (!await downloadFontFile(url, destPath))
|
|
2286
2305
|
continue;
|
|
2287
2306
|
results.push({ family, weight, style, format, filename, url });
|
|
@@ -2294,51 +2313,175 @@ async function resolveAndDownloadFamily(options) {
|
|
|
2294
2313
|
}
|
|
2295
2314
|
return results;
|
|
2296
2315
|
}
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2316
|
+
function hasOpenTypeTable(data, expectedTag) {
|
|
2317
|
+
if (data.byteLength < 12)
|
|
2318
|
+
return false;
|
|
2319
|
+
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
2320
|
+
const tableCount = view.getUint16(4);
|
|
2321
|
+
for (let index = 0; index < tableCount; index++) {
|
|
2322
|
+
const offset = 12 + index * 16;
|
|
2323
|
+
if (offset + 4 > data.byteLength)
|
|
2324
|
+
return false;
|
|
2325
|
+
const tag = String.fromCharCode(
|
|
2326
|
+
view.getUint8(offset),
|
|
2327
|
+
view.getUint8(offset + 1),
|
|
2328
|
+
view.getUint8(offset + 2),
|
|
2329
|
+
view.getUint8(offset + 3)
|
|
2330
|
+
);
|
|
2331
|
+
if (tag === expectedTag)
|
|
2332
|
+
return true;
|
|
2333
|
+
}
|
|
2334
|
+
return false;
|
|
2335
|
+
}
|
|
2336
|
+
function getNuxtFontOriginalSource(fontSrc, context) {
|
|
2337
|
+
if (!context)
|
|
2338
|
+
return;
|
|
2339
|
+
const prefix = `${context.assetsBaseURL.replace(/\/$/, "")}/`;
|
|
2340
|
+
if (!fontSrc.startsWith(prefix))
|
|
2341
|
+
return;
|
|
2342
|
+
return context.renderedFontURLs.get(fontSrc.slice(prefix.length));
|
|
2343
|
+
}
|
|
2344
|
+
async function readNuxtFontAsset(nuxt, source) {
|
|
2345
|
+
if (source.startsWith("/")) {
|
|
2346
|
+
const configuredPublicDir = nuxt.options.dir.public || "public";
|
|
2347
|
+
const publicDir = isAbsolute(configuredPublicDir) ? configuredPublicDir : join(nuxt.options.srcDir, configuredPublicDir);
|
|
2348
|
+
const path = join(publicDir, source.slice(1).split(/[?#]/, 1)[0]);
|
|
2349
|
+
return fs.promises.readFile(path).then((data) => ({ _tag: "Ok", data })).catch((error) => ({
|
|
2350
|
+
_tag: "Err",
|
|
2351
|
+
reason: error.code === "ENOENT" ? `local source not found: ${path}` : error.message
|
|
2352
|
+
}));
|
|
2353
|
+
}
|
|
2354
|
+
if (source.startsWith("file:")) {
|
|
2355
|
+
const path = fileURLToPath(source);
|
|
2356
|
+
return fs.promises.readFile(path).then((data) => ({ _tag: "Ok", data })).catch((error) => ({ _tag: "Err", reason: error.message }));
|
|
2357
|
+
}
|
|
2358
|
+
return fetch(source).then(async (response) => response.ok ? { _tag: "Ok", data: new Uint8Array(await response.arrayBuffer()) } : { _tag: "Err", reason: `source returned HTTP ${response.status}` }).catch((error) => ({ _tag: "Err", reason: error.message }));
|
|
2359
|
+
}
|
|
2360
|
+
async function convertNuxtWoff2Sources(options) {
|
|
2361
|
+
const staticFontDir = getStaticFontCacheDir(options.nuxt.options.buildDir);
|
|
2362
|
+
fs.mkdirSync(staticFontDir, { recursive: true });
|
|
2363
|
+
const fontsBySource = /* @__PURE__ */ new Map();
|
|
2364
|
+
for (const font of options.fonts) {
|
|
2365
|
+
if (!font.weightRange && !fontsBySource.has(font.src))
|
|
2366
|
+
fontsBySource.set(font.src, font);
|
|
2367
|
+
}
|
|
2368
|
+
const mappedSources = [];
|
|
2369
|
+
for (const [fontSrc, font] of fontsBySource) {
|
|
2370
|
+
const originalSource = getNuxtFontOriginalSource(fontSrc, options.context) || (isConfiguredLocalFontFamily(options.nuxt, font.family) ? fontSrc : void 0);
|
|
2371
|
+
if (originalSource)
|
|
2372
|
+
mappedSources.push({ font, fontSrc, originalSource });
|
|
2373
|
+
else
|
|
2374
|
+
options.logger.debug(`Could not map Nuxt Fonts asset to its original source: ${fontSrc}`);
|
|
2375
|
+
}
|
|
2376
|
+
if (mappedSources.length === 0)
|
|
2377
|
+
return;
|
|
2378
|
+
const { woff2Decode } = await import('../chunks/decode.mjs');
|
|
2379
|
+
for (const { fontSrc, font, originalSource } of mappedSources) {
|
|
2380
|
+
const asset = await readNuxtFontAsset(options.nuxt, originalSource);
|
|
2381
|
+
if (asset._tag === "Err") {
|
|
2382
|
+
options.logger.debug(`Could not read Nuxt Fonts asset ${fontSrc}: ${asset.reason}`);
|
|
2383
|
+
continue;
|
|
2384
|
+
}
|
|
2385
|
+
const decoded = await Promise.resolve().then(() => woff2Decode(asset.data)).catch((error) => {
|
|
2386
|
+
options.logger.warn(`Failed to decode Nuxt Fonts asset ${fontSrc}: ${error.message}`);
|
|
2387
|
+
return null;
|
|
2388
|
+
});
|
|
2389
|
+
if (!decoded)
|
|
2390
|
+
continue;
|
|
2391
|
+
if (hasOpenTypeTable(decoded, "fvar")) {
|
|
2392
|
+
options.logger.debug(`${font.family}: ${fontSrc} is variable; a static Satori fallback is required`);
|
|
2393
|
+
continue;
|
|
2394
|
+
}
|
|
2395
|
+
const sourceFilename = fontSrc.split("/").pop();
|
|
2396
|
+
const filename = sourceFilename.replace(/\.woff2$/i, ".ttf");
|
|
2397
|
+
await fs.promises.writeFile(join(staticFontDir, filename), decoded);
|
|
2398
|
+
options.fontState.sourceMap.set(fontSrc, `${STATIC_FONTS_PREFIX}/${filename}`);
|
|
2399
|
+
}
|
|
2400
|
+
if (options.fontState.sourceMap.size > 0)
|
|
2401
|
+
options.logger.debug(`Converted ${options.fontState.sourceMap.size} Nuxt Fonts WOFF2 assets to TTF`);
|
|
2402
|
+
}
|
|
2403
|
+
async function prepareWoff2Fonts(options) {
|
|
2404
|
+
const { nuxt, logger, fontRequirements, fontState, nuxtFontsContext, fontSubsets, warnOnMissingStaticFonts = true } = options;
|
|
2405
|
+
const parsedFonts = await parseFontsFromTemplate(nuxt, { fontState });
|
|
2406
|
+
const requirementsByFamily = /* @__PURE__ */ new Map();
|
|
2407
|
+
for (const font of parsedFonts) {
|
|
2408
|
+
if (!requirementsByFamily.has(font.family))
|
|
2409
|
+
requirementsByFamily.set(font.family, getFamilyRequirements(fontRequirements, font.family));
|
|
2410
|
+
}
|
|
2300
2411
|
const hasNonWoff2 = new Set(
|
|
2301
2412
|
parsedFonts.filter((f) => !f.src.endsWith(".woff2")).map((f) => fontKey(f))
|
|
2302
2413
|
);
|
|
2303
|
-
const woff2Fonts = parsedFonts.filter(
|
|
2304
|
-
|
|
2305
|
-
|
|
2414
|
+
const woff2Fonts = parsedFonts.filter((font) => {
|
|
2415
|
+
const requirements = requirementsByFamily.get(font.family);
|
|
2416
|
+
return font.src.endsWith(".woff2") && !hasNonWoff2.has(fontKey(font)) && requirements.weights.includes(font.weight) && requirements.styles.includes(font.style);
|
|
2417
|
+
});
|
|
2306
2418
|
if (woff2Fonts.length === 0) {
|
|
2307
2419
|
logger.debug("No WOFF2 fonts to process");
|
|
2308
2420
|
return;
|
|
2309
2421
|
}
|
|
2310
|
-
|
|
2422
|
+
await convertNuxtWoff2Sources({
|
|
2423
|
+
fonts: woff2Fonts,
|
|
2424
|
+
nuxt,
|
|
2425
|
+
context: nuxtFontsContext,
|
|
2426
|
+
fontState,
|
|
2427
|
+
logger
|
|
2428
|
+
});
|
|
2429
|
+
const configuredLocalFamilies = new Set(
|
|
2430
|
+
woff2Fonts.filter((font) => isConfiguredLocalFontFamily(nuxt, font.family)).map((font) => font.family)
|
|
2431
|
+
);
|
|
2432
|
+
const unresolvedLocalFamilies = new Set(
|
|
2433
|
+
woff2Fonts.filter((font) => configuredLocalFamilies.has(font.family) && !fontState.sourceMap.has(font.src)).map((font) => font.family)
|
|
2434
|
+
);
|
|
2435
|
+
if (warnOnMissingStaticFonts) {
|
|
2436
|
+
for (const family of unresolvedLocalFamilies)
|
|
2437
|
+
logger.warn(`Configured Nuxt Fonts assets for "${family}" could not be converted for Satori. Satori does not support WOFF2 or variable fonts; use static WOFF/TTF sources or the Takumi renderer.`);
|
|
2438
|
+
}
|
|
2439
|
+
const unavailableStaticFonts = new Set(
|
|
2440
|
+
woff2Fonts.filter((font) => !fontState.sourceMap.has(font.src)).map((font) => `${font.family}-${font.weight}-${font.style}`)
|
|
2441
|
+
);
|
|
2442
|
+
const availableStaticFonts = new Set(
|
|
2443
|
+
parsedFonts.filter((font) => !unavailableStaticFonts.has(`${font.family}-${font.weight}-${font.style}`)).filter((font) => !font.src.endsWith(".woff2") || fontState.sourceMap.has(font.src)).map((font) => `${font.family}-${font.weight}-${font.style}`)
|
|
2444
|
+
);
|
|
2445
|
+
const fallbackRequests = /* @__PURE__ */ new Map();
|
|
2311
2446
|
for (const font of woff2Fonts) {
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2447
|
+
if (configuredLocalFamilies.has(font.family))
|
|
2448
|
+
continue;
|
|
2449
|
+
const style = font.style;
|
|
2450
|
+
const requirements = requirementsByFamily.get(font.family);
|
|
2451
|
+
const key = `${font.family}\0${style}`;
|
|
2452
|
+
const request = fallbackRequests.get(key) || { family: font.family, weights: /* @__PURE__ */ new Set(), style };
|
|
2453
|
+
for (const weight of requirements.weights) {
|
|
2454
|
+
if (!availableStaticFonts.has(`${font.family}-${weight}-${style}`))
|
|
2455
|
+
request.weights.add(weight);
|
|
2456
|
+
}
|
|
2457
|
+
fallbackRequests.set(key, request);
|
|
2315
2458
|
}
|
|
2316
|
-
const families = Array.from(
|
|
2459
|
+
const families = Array.from(fallbackRequests.values(), ({ family, weights, style }) => ({
|
|
2317
2460
|
family,
|
|
2318
2461
|
weights: [...weights],
|
|
2319
|
-
styles: [
|
|
2320
|
-
}));
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
logger.warn(`No static fonts available for Satori. Falling back to bundled Inter font. Consider using 'takumi' renderer for variable font support.`);
|
|
2338
|
-
}
|
|
2339
|
-
} catch (err) {
|
|
2340
|
-
logger.debug("fontless resolution failed:", err);
|
|
2462
|
+
styles: [style]
|
|
2463
|
+
})).filter((family) => family.weights.length > 0);
|
|
2464
|
+
if (families.length === 0)
|
|
2465
|
+
return;
|
|
2466
|
+
logger.debug(`Resolving static font fallbacks for: ${families.map((f) => f.family).join(", ")}`);
|
|
2467
|
+
const downloaded = await downloadStaticFonts({
|
|
2468
|
+
families,
|
|
2469
|
+
nuxt,
|
|
2470
|
+
logger,
|
|
2471
|
+
fontSubsets,
|
|
2472
|
+
warnOnMissingStaticFonts
|
|
2473
|
+
}).catch((error) => {
|
|
2474
|
+
logger.debug("fontless resolution failed:", error);
|
|
2475
|
+
return [];
|
|
2476
|
+
});
|
|
2477
|
+
for (const font of downloaded) {
|
|
2478
|
+
const key = `${font.family}-${font.weight}-${font.style}`;
|
|
2479
|
+
fontState.fallbackMap.set(key, `${STATIC_FONTS_PREFIX}/${font.filename}`);
|
|
2341
2480
|
}
|
|
2481
|
+
if (fontState.fallbackMap.size > 0)
|
|
2482
|
+
logger.debug(`Resolved ${fontState.fallbackMap.size} static font fallbacks via fontless`);
|
|
2483
|
+
else if (warnOnMissingStaticFonts && fontState.sourceMap.size === 0)
|
|
2484
|
+
logger.warn(`No static fonts available for Satori. Falling back to bundled Inter font. Consider using the Takumi renderer for variable font support.`);
|
|
2342
2485
|
}
|
|
2343
2486
|
async function resolveMissingFontFamilies(options) {
|
|
2344
2487
|
const { missingFamilies, weights, styles, nuxt, logger, fontSubsets } = options;
|
|
@@ -2356,9 +2499,9 @@ async function resolveMissingFontFamilies(options) {
|
|
|
2356
2499
|
return results;
|
|
2357
2500
|
}
|
|
2358
2501
|
async function resolveOgImageFonts(options) {
|
|
2359
|
-
const { nuxt, hasNuxtFonts, hasSatoriRenderer, hasTakumiRenderer,
|
|
2502
|
+
const { nuxt, hasNuxtFonts, hasSatoriRenderer, hasTakumiRenderer, fontState, fontSubsets, fontRequirements, tw4FontVars, logger, ogFontsDir } = options;
|
|
2360
2503
|
const staticInterFonts = getStaticInterFonts(ogFontsDir);
|
|
2361
|
-
const allFonts = hasNuxtFonts ? await parseFontsFromTemplate(nuxt, {
|
|
2504
|
+
const allFonts = hasNuxtFonts ? await parseFontsFromTemplate(nuxt, { fontState, requiredWeights: fontRequirements.weights }) : [];
|
|
2362
2505
|
if (hasNuxtFonts) {
|
|
2363
2506
|
const configuredLocalFonts = parseConfiguredLocalFonts(nuxt);
|
|
2364
2507
|
if (configuredLocalFonts.length > 0) {
|
|
@@ -2413,7 +2556,7 @@ async function resolveOgImageFonts(options) {
|
|
|
2413
2556
|
}
|
|
2414
2557
|
}
|
|
2415
2558
|
const nuxtFontFamilies = new Set(
|
|
2416
|
-
hasNuxtFonts ? (await parseFontsFromTemplate(nuxt, {
|
|
2559
|
+
hasNuxtFonts ? (await parseFontsFromTemplate(nuxt, { fontState, requiredWeights: fontRequirements.weights })).map((f) => f.family) : []
|
|
2417
2560
|
);
|
|
2418
2561
|
const fonts = !fontRequirements.hasDynamicBindings ? allFonts.filter(
|
|
2419
2562
|
(f) => nuxtFontFamilies.has(f.family) ? fontRequirements.styles.includes(f.style) : matchesFontRequirements(f, fontRequirements)
|
|
@@ -4581,6 +4724,7 @@ declare module '#og-image-virtual/component-names.mjs' {
|
|
|
4581
4724
|
declare module '#og-image-virtual/build-dir.mjs' {
|
|
4582
4725
|
export const buildDir: string
|
|
4583
4726
|
export const rootDir: string
|
|
4727
|
+
export const staticFontCacheDir: string
|
|
4584
4728
|
}
|
|
4585
4729
|
|
|
4586
4730
|
declare module '#og-image/compatibility' {
|
|
@@ -4666,8 +4810,8 @@ declare module '#og-image-cache' {
|
|
|
4666
4810
|
NitroApp: `_ogImageCacheBackendWarned?: boolean
|
|
4667
4811
|
_ogImageIconsData?: OgImageIconsData`,
|
|
4668
4812
|
NitroRuntimeConfig: `'nuxt-og-image': OgImageRuntimeConfig
|
|
4669
|
-
ogImage
|
|
4670
|
-
secret
|
|
4813
|
+
ogImage: {
|
|
4814
|
+
secret: string
|
|
4671
4815
|
}`
|
|
4672
4816
|
},
|
|
4673
4817
|
routeRules: "ogImage?: false | OgImageOptions & Record<string, any>",
|
|
@@ -5074,7 +5218,10 @@ Or pin to a specific version in package.json:
|
|
|
5074
5218
|
const resolvedOgComponentPaths = [];
|
|
5075
5219
|
let allNuxtComponents = [];
|
|
5076
5220
|
const ogImageTransformedFiles = /* @__PURE__ */ new Set();
|
|
5077
|
-
const
|
|
5221
|
+
const fontState = {
|
|
5222
|
+
sourceMap: /* @__PURE__ */ new Map(),
|
|
5223
|
+
fallbackMap: /* @__PURE__ */ new Map()
|
|
5224
|
+
};
|
|
5078
5225
|
const ogImageComponentCtx = { components: [], detectedRenderers: /* @__PURE__ */ new Set() };
|
|
5079
5226
|
nuxt.hook("modules:done", () => {
|
|
5080
5227
|
addVitePlugin(ComponentImportRewritePlugin.vite({
|
|
@@ -5101,7 +5248,7 @@ Or pin to a specific version in package.json:
|
|
|
5101
5248
|
const p = loadCssMetadata().then(async () => {
|
|
5102
5249
|
let knownFamilies;
|
|
5103
5250
|
if (hasNuxtFonts) {
|
|
5104
|
-
const resolvedFonts = await parseFontsFromTemplate(nuxt, {
|
|
5251
|
+
const resolvedFonts = await parseFontsFromTemplate(nuxt, { fontState });
|
|
5105
5252
|
knownFamilies = buildFontFamilyCanonicalMap(resolvedFonts.map((f) => f.family));
|
|
5106
5253
|
}
|
|
5107
5254
|
const families = resolveFontFamilies([...reqs.familyClasses], [...reqs.familyNames], cssMetadata.fontVars, knownFamilies);
|
|
@@ -5567,9 +5714,9 @@ export function getIslandHash({ name, props, context, source }) {
|
|
|
5567
5714
|
}`;
|
|
5568
5715
|
};
|
|
5569
5716
|
nuxt.options.nitro.publicAssets ||= [];
|
|
5570
|
-
const
|
|
5717
|
+
const staticFontCacheDir = getStaticFontCacheDir(nuxt.options.buildDir);
|
|
5571
5718
|
nuxt.options.nitro.publicAssets.push({
|
|
5572
|
-
dir:
|
|
5719
|
+
dir: staticFontCacheDir,
|
|
5573
5720
|
baseURL: "/_og-static-fonts",
|
|
5574
5721
|
maxAge: 60 * 60 * 24 * 365
|
|
5575
5722
|
});
|
|
@@ -5607,24 +5754,26 @@ export const resolve = (import.meta.dev || import.meta.prerender) ? devResolve :
|
|
|
5607
5754
|
if (hasNuxtFonts && fontContext) {
|
|
5608
5755
|
persistFontUrlMapping({ fontContext, buildDir: nuxt.options.buildDir, logger });
|
|
5609
5756
|
}
|
|
5610
|
-
if (!fontProcessingDone &&
|
|
5757
|
+
if (!fontProcessingDone && hasSatoriRenderer() && hasNuxtFonts) {
|
|
5611
5758
|
if (pendingFontRequirements.length > 0)
|
|
5612
5759
|
await Promise.all(pendingFontRequirements);
|
|
5613
|
-
await
|
|
5760
|
+
await prepareWoff2Fonts({
|
|
5614
5761
|
nuxt,
|
|
5615
5762
|
logger,
|
|
5616
5763
|
fontRequirements: fontRequirementsState,
|
|
5617
|
-
|
|
5764
|
+
fontState,
|
|
5765
|
+
nuxtFontsContext: fontContext,
|
|
5618
5766
|
fontSubsets: config.fontSubsets,
|
|
5619
5767
|
warnOnMissingStaticFonts: hasSatoriRenderer()
|
|
5620
5768
|
});
|
|
5769
|
+
fontProcessingDone = true;
|
|
5621
5770
|
}
|
|
5622
5771
|
const fonts = await resolveOgImageFonts({
|
|
5623
5772
|
nuxt,
|
|
5624
5773
|
hasNuxtFonts,
|
|
5625
5774
|
hasSatoriRenderer: hasSatoriRenderer(),
|
|
5626
5775
|
hasTakumiRenderer: hasTakumiRenderer(),
|
|
5627
|
-
|
|
5776
|
+
fontState,
|
|
5628
5777
|
fontSubsets: config.fontSubsets,
|
|
5629
5778
|
fontRequirements: fontRequirementsState,
|
|
5630
5779
|
tw4FontVars: cssMetadata.fontVars,
|
|
@@ -5634,7 +5783,7 @@ export const resolve = (import.meta.dev || import.meta.prerender) ? devResolve :
|
|
|
5634
5783
|
return `export default ${JSON.stringify(fonts)}`;
|
|
5635
5784
|
};
|
|
5636
5785
|
nuxt.options.nitro.virtual["#og-image/fonts-available"] = async () => {
|
|
5637
|
-
const fonts = hasNuxtFonts ? await parseFontsFromTemplate(nuxt, {
|
|
5786
|
+
const fonts = hasNuxtFonts ? await parseFontsFromTemplate(nuxt, { fontState, requiredWeights: fontRequirementsState.weights }) : [];
|
|
5638
5787
|
return `export default ${JSON.stringify(fonts)}`;
|
|
5639
5788
|
};
|
|
5640
5789
|
nuxt.options.nitro.virtual["#og-image/font-requirements"] = async () => {
|
|
@@ -5669,7 +5818,8 @@ export const tw4Colors = ${JSON.stringify(cssMetadata.colors)}`;
|
|
|
5669
5818
|
};
|
|
5670
5819
|
nuxt.options.nitro.virtual["#og-image-virtual/build-dir.mjs"] = () => {
|
|
5671
5820
|
return `export const buildDir = ${JSON.stringify(nuxt.options.buildDir)}
|
|
5672
|
-
export const rootDir = ${JSON.stringify(nuxt.options.rootDir)}
|
|
5821
|
+
export const rootDir = ${JSON.stringify(nuxt.options.rootDir)}
|
|
5822
|
+
export const staticFontCacheDir = ${JSON.stringify(getStaticFontCacheDir(nuxt.options.buildDir))}`;
|
|
5673
5823
|
};
|
|
5674
5824
|
if (hasNuxtFonts) {
|
|
5675
5825
|
nuxt.hook("fonts:public-asset-context", (ctx) => {
|
|
@@ -5677,16 +5827,17 @@ export const rootDir = ${JSON.stringify(nuxt.options.rootDir)}`;
|
|
|
5677
5827
|
});
|
|
5678
5828
|
nuxt.hook("vite:compiled", async () => {
|
|
5679
5829
|
persistFontUrlMapping({ fontContext, buildDir: nuxt.options.buildDir, logger });
|
|
5680
|
-
if (fontProcessingDone || !hasSatoriRenderer()
|
|
5830
|
+
if (fontProcessingDone || !hasSatoriRenderer())
|
|
5681
5831
|
return;
|
|
5682
5832
|
if (pendingFontRequirements.length === 0)
|
|
5683
5833
|
return;
|
|
5684
5834
|
await Promise.all(pendingFontRequirements);
|
|
5685
|
-
await
|
|
5835
|
+
await prepareWoff2Fonts({
|
|
5686
5836
|
nuxt,
|
|
5687
5837
|
logger,
|
|
5688
5838
|
fontRequirements: fontRequirementsState,
|
|
5689
|
-
|
|
5839
|
+
fontState,
|
|
5840
|
+
nuxtFontsContext: fontContext,
|
|
5690
5841
|
fontSubsets: config.fontSubsets,
|
|
5691
5842
|
warnOnMissingStaticFonts: hasSatoriRenderer()
|
|
5692
5843
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nuxt-og-image",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "6.7.
|
|
4
|
+
"version": "6.7.7",
|
|
5
5
|
"description": "Enlightened OG Image generation for Nuxt.",
|
|
6
6
|
"author": {
|
|
7
7
|
"website": "https://harlanzw.com",
|
|
@@ -111,8 +111,8 @@
|
|
|
111
111
|
},
|
|
112
112
|
"dependencies": {
|
|
113
113
|
"@clack/prompts": "^1.7.0",
|
|
114
|
-
"@nuxt/kit": "^4.5.
|
|
115
|
-
"@vue/compiler-sfc": "^3.5.
|
|
114
|
+
"@nuxt/kit": "^4.5.2",
|
|
115
|
+
"@vue/compiler-sfc": "^3.5.41",
|
|
116
116
|
"chrome-launcher": "^1.2.1",
|
|
117
117
|
"consola": "^3.4.2",
|
|
118
118
|
"defu": "^6.1.7",
|
|
@@ -123,13 +123,13 @@
|
|
|
123
123
|
"magic-string": "^1.1.0",
|
|
124
124
|
"magicast": "^0.5.4",
|
|
125
125
|
"mocked-exports": "^0.1.1",
|
|
126
|
-
"nuxt-site-config": "^4.
|
|
127
|
-
"nuxtseo-shared": "^5.3.
|
|
126
|
+
"nuxt-site-config": "^4.2.0",
|
|
127
|
+
"nuxtseo-shared": "^5.3.12",
|
|
128
128
|
"nypm": "^0.6.9",
|
|
129
129
|
"object-identity": "^0.2.3",
|
|
130
130
|
"ofetch": "^1.5.1",
|
|
131
131
|
"ohash": "^2.0.11",
|
|
132
|
-
"oxc-parser": "^0.
|
|
132
|
+
"oxc-parser": "^0.143.0",
|
|
133
133
|
"oxc-walker": "^1.1.1",
|
|
134
134
|
"pathe": "^2.0.3",
|
|
135
135
|
"pkg-types": "^2.3.1",
|
|
@@ -142,10 +142,10 @@
|
|
|
142
142
|
"unplugin": "^3.3.0"
|
|
143
143
|
},
|
|
144
144
|
"devDependencies": {
|
|
145
|
-
"@antfu/eslint-config": "^9.
|
|
145
|
+
"@antfu/eslint-config": "^9.3.0",
|
|
146
146
|
"@fontsource/noto-sans-sc": "^5.3.0",
|
|
147
147
|
"@iconify-json/carbon": "^1.2.25",
|
|
148
|
-
"@iconify-json/logos": "^1.2.
|
|
148
|
+
"@iconify-json/logos": "^1.2.12",
|
|
149
149
|
"@iconify-json/noto": "^1.2.7",
|
|
150
150
|
"@iconify-json/ri": "^1.2.10",
|
|
151
151
|
"@iconify-json/tabler": "^1.2.38",
|
|
@@ -167,11 +167,11 @@
|
|
|
167
167
|
"@resvg/resvg-js": "^2.6.2",
|
|
168
168
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
169
169
|
"@tailwindcss/vite": "^4.3.3",
|
|
170
|
-
"@takumi-rs/core": "^2.5.
|
|
170
|
+
"@takumi-rs/core": "^2.5.11",
|
|
171
171
|
"@takumi-rs/core-v1": "npm:@takumi-rs/core@1.8.7",
|
|
172
|
-
"@takumi-rs/helpers": "^2.5.
|
|
172
|
+
"@takumi-rs/helpers": "^2.5.11",
|
|
173
173
|
"@takumi-rs/helpers-v1": "npm:@takumi-rs/helpers@1.8.7",
|
|
174
|
-
"@takumi-rs/wasm": "^2.5.
|
|
174
|
+
"@takumi-rs/wasm": "^2.5.11",
|
|
175
175
|
"@takumi-rs/wasm-v1": "npm:@takumi-rs/wasm@1.8.7",
|
|
176
176
|
"@unocss/config": "^66.7.5",
|
|
177
177
|
"@unocss/core": "^66.7.5",
|
|
@@ -180,22 +180,21 @@
|
|
|
180
180
|
"@unocss/preset-wind": "^66.7.5",
|
|
181
181
|
"@unocss/preset-wind4": "^66.7.5",
|
|
182
182
|
"@vitejs/plugin-vue": "^6.0.8",
|
|
183
|
-
"@vue/compiler-core": "^3.5.
|
|
183
|
+
"@vue/compiler-core": "^3.5.41",
|
|
184
184
|
"@vueuse/nuxt": "^14.4.0",
|
|
185
185
|
"birpc": "^4.0.0",
|
|
186
|
-
"bumpp": "^12.
|
|
187
|
-
"eslint": "^10.8.
|
|
186
|
+
"bumpp": "^12.2.0",
|
|
187
|
+
"eslint": "^10.8.1",
|
|
188
188
|
"eslint-plugin-harlanzw": "^0.17.1",
|
|
189
189
|
"fontless": "^0.2.1",
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
"happy-dom": "^20.11.1",
|
|
190
|
+
"globby": "^16.2.3",
|
|
191
|
+
"happy-dom": "^20.11.2",
|
|
193
192
|
"hookable": "^6.1.1",
|
|
194
193
|
"jest-image-snapshot": "^6.5.2",
|
|
195
194
|
"lightningcss": "^1.33.0",
|
|
196
195
|
"nitropack": "^2.13.4",
|
|
197
|
-
"nuxt": "^4.5.
|
|
198
|
-
"nuxtseo-layer-devtools": "^5.3.
|
|
196
|
+
"nuxt": "^4.5.2",
|
|
197
|
+
"nuxtseo-layer-devtools": "^5.3.12",
|
|
199
198
|
"playwright": "^1.62.1",
|
|
200
199
|
"playwright-core": "^1.62.1",
|
|
201
200
|
"sass": "^1.102.0",
|
|
@@ -208,9 +207,9 @@
|
|
|
208
207
|
"unifont": "^0.7.4",
|
|
209
208
|
"unocss": "^66.7.5",
|
|
210
209
|
"vitest": "^4.1.10",
|
|
211
|
-
"vue": "^3.5.
|
|
210
|
+
"vue": "^3.5.41",
|
|
212
211
|
"vue-tsc": "^3.3.9",
|
|
213
|
-
"wrangler": "^4.
|
|
212
|
+
"wrangler": "^4.120.0",
|
|
214
213
|
"yoga-wasm-web": "^0.3.3",
|
|
215
214
|
"zod": "^4.4.3"
|
|
216
215
|
},
|
|
@@ -230,7 +229,7 @@
|
|
|
230
229
|
"typecheck": "pnpm stub && nuxt typecheck",
|
|
231
230
|
"test": "pnpm run prepare:fixtures && vitest",
|
|
232
231
|
"test:run": "pnpm test -- --run && pnpm test:nuxt5",
|
|
233
|
-
"test:nuxt5": "pnpm pack --out test/fixtures/nuxt5/nuxt-og-image.tgz && pnpm --dir test/fixtures/nuxt5
|
|
232
|
+
"test:nuxt5": "pnpm pack --out test/fixtures/nuxt5/nuxt-og-image.tgz && pnpm install --dir test/fixtures/nuxt5 --no-frozen-lockfile --force --update-checksums && pnpm --dir test/fixtures/nuxt5 test",
|
|
234
233
|
"test:chrome": "HAS_CHROME=true vitest",
|
|
235
234
|
"test:packed-install": "node scripts/smoke-packed-install.mjs",
|
|
236
235
|
"test:takumi:e2e": "node scripts/test-takumi-e2e.mjs",
|