diff --git a/src/cli.ts b/src/cli.ts index 877ef47..b26daae 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -11,29 +11,34 @@ function splitList(value: string): number[] { program .argument("", "File or folder to process") .option( - "--png-quality ", + "-p, --png-quality ", "Set PNG quality", Number, DEFAULTS.pngQuality ) .option( - "--manifest-icon-sizes ", + "-m, --manifest-icon-sizes ", "Set manifest icon sizes (comma-separated)", splitList, DEFAULTS.manifestIconSizes ) .option( - "--apple-icon-sizes ", + "-a, --apple-icon-sizes ", "Set Apple icon sizes (comma-separated)", splitList, DEFAULTS.appleIconSizes ) .option( - "--fav-icon-sizes ", + "-s, --fav-icon-sizes ", "Set favicon sizes (comma-separated)", splitList, DEFAULTS.favIconSizes ) + .option( + "--no-inline-manifest-icons", + "Disable inlining manifest icons", + DEFAULTS.inlineManifestIcons + ) .parse(process.argv) // Parse and normalize the options diff --git a/src/factory/manifest.ts b/src/factory/manifest.ts index c795469..add686d 100644 --- a/src/factory/manifest.ts +++ b/src/factory/manifest.ts @@ -1,5 +1,5 @@ import { promises as fs } from "node:fs" -import { basename } from "node:path" +import { extname } from "node:path" import { readPackageUp } from "read-package-up" import sharp from "sharp" @@ -7,6 +7,28 @@ import sharp from "sharp" import type { Options } from "../options.js" import type { WebManifest, WebManifestIcon } from "../types.js" +/** + * Converts an image file to a base64 data URL string. + * @param filePath - The path to the image file. + * @returns A promise that resolves to the base64 data URL string. + */ +async function inlineIcon(filePath: string): Promise { + try { + const data = await fs.readFile(filePath) + const ext = extname(filePath).slice(1) // Get the file extension without the dot + const mimeType = `image/${ext}` + const base64Data = data.toString("base64") + const dataUrl = `data:${mimeType};base64,${base64Data}` + return dataUrl + } catch (error) { + const wrapped = + error instanceof Error + ? new Error(`Failed to read file: ${error.message}`) + : new Error("An unknown error occurred") + throw wrapped + } +} + export async function generateWebManifest( filePrefix: string, svgContent: string, @@ -16,7 +38,7 @@ export async function generateWebManifest( const icons: WebManifestIcon[] = [] const manifestData: WebManifest = { name: pkg?.packageJson.description, - start_url: ".", + start_url: "/", scope: "/", display: "browser", icons @@ -24,11 +46,6 @@ export async function generateWebManifest( for (const size of options.manifestIconSizes) { const pngFilePath = `${filePrefix}-pwa-${size}.png` - icons.push({ - src: basename(pngFilePath), - type: "image/png", - sizes: `${size}x${size}` - }) await sharp(Buffer.from(svgContent)) .resize(size, size) @@ -39,6 +56,14 @@ export async function generateWebManifest( quality: options.pngQuality }) .toFile(pngFilePath) + + icons.push({ + src: options.inlineManifestIcons + ? await inlineIcon(pngFilePath) + : basename(pngFilePath), + type: "image/png", + sizes: `${size}x${size}` + }) } const manifestPath = `${filePrefix}.webmanifest` diff --git a/src/options.ts b/src/options.ts index 13cfcde..2d2e462 100644 --- a/src/options.ts +++ b/src/options.ts @@ -2,7 +2,8 @@ export const DEFAULTS = { pngQuality: 95, manifestIconSizes: [192, 512], appleIconSizes: [152, 167, 180], - favIconSizes: [16, 32] + favIconSizes: [16, 32], + inlineManifestIcons: true } export type Options = typeof DEFAULTS diff --git a/test/sebastian-software.webmanifest b/test/sebastian-software.webmanifest index 19cdf51..147e344 100644 --- a/test/sebastian-software.webmanifest +++ b/test/sebastian-software.webmanifest @@ -1,16 +1,16 @@ { "name": "Effective Favicon Generator", - "start_url": ".", + "start_url": "/", "scope": "/", "display": "browser", "icons": [ { - "src": "sebastian-software-pwa-192.png", + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMAAAADACAMAAABlApw1AAAAV1BMVEUqGifz8vNFOEPPy87FAEQzJTH8/PwpGicqGyj///9VSVTl4+XIxMeHCzl5cHhwZm97DTc9Lzvb2NpgVV83GCqsBD9LFS65tLm8AUKln6RiETKSipF+DDdi0SGKAAAACHRSTlPm////////7vrFWCYAAAAJcEhZcwAACxMAAAsTAQCanBgAAARLSURBVHja7d1tc6IwFIZhRWGNWhVQEe3//51b123rC8Gc5DwnJzPhqzOd+7JCEkCcLNLeJpMMyIAMyIAMyIAMyIAMyADbVtRpA4pVU6cMKFbGYAVYwLUfLIACbv1YARLw3Q8VAAG//UgBDnDfDxTAAI/9OAEK8Nz/JShTArz2G9MlBBjqN2aZDGC431xSAVj6MZ8hAMDWnwrA2m/6JAD2ftOmABjpn6VwGB3pnxYJAMb6IaMAMyBCPysgRj8nIEo/IyBOPx8gUj8bIFY/FyBaPxMgXj8PIGI/CyBmPwcgaj8DIG5/OCByfzAgdn8oIHp/ICB+fxhAQX8QQEN/CEBFfwBAR78/QEm/N0BLvy9ATb8nQE+/H0BRvxdAU78PgNZfXLqubzUBaP19dbtEqefMHKm/XP+85vJPOJ7P5z0Y4NnvIjgf5l/b6RMK8O5/L9jN/2+HPQ4Q0P9O8NNPFJAAQf3jx9i7fpqAAgjsN2ZVOvWTBARAcL/9Wv1TP0XgDmDot12ofOknCJwBHP1m6trvLnAFlA1D/zBgsN9Z4AroOfpN497vKnAElFOO/qGd+HNu3XaMgCVL/7R2f//n8w/Oj9CWo7/a8ve7AlqO/g2g33kfqJT2Ox+FOqX9zoBiqrPffSRupyr7CXOhJ4GSfsps9EGgpZ+0HrgTqOmnrch+BCs1/dQ1cXcdD6q+VtNPPq1St5v2dWEYr5/nZo+I/SyAmP0cgKj9DIC4/eGAyP3BgNj9oYDo/YGA+P1hAAX9QQAN/SEAFf0BAB39/gAl/d4ALf2+ADX9ngA9/X4ARf1eAE39PgBk/3Jz2RRgALB/Ofv3Z9YFEgDs31aUO0M8ARL9JAERINNPEdAAUv0EAQkg1+8uoAAk+wcvaQYCZPtdv4LvDpDut9xY4Q0Q7zemYL3ZQ77f7VEmrteJSf1Hln5TMwJ6Sv/ig6W/4dwH6obQvzhx9JsN605sEQz271n618wD2aCgGn6XOPpnJfdIPCCw9C8Ocv2UkfhFYOsfPopi+klzoSeBtX+xP4n102ajD4Jq5ChxPEn1E9cDd4Jq9Cj3LID1U1dk9cx+/9uIANdPXxNfrvdLVOu385R7AbDf57zQctu6zFJ+Bch+5NMuj7fh4HRG9mMfmHrcfXwOfKuHsz/GY5tZ+yMAePvlAcz94gDufmkAe78wgL9fFgDoFwUg+iUBkH5BAKZfDgDqFwOg+qUAsH4hAK5fBgDsFwEg+yUA0H4BALYfDwD3wwHofjQA3g8G4PuxAIF+KECiHwkQ6QcCZPpxAKF+GKAV6kcBypVQPwqwkepHATqpfhSgkepHAWZS/ShAL9WPArRS/bBxoBPqhwFeH6g0K9P6Sbvne1tA/cDJ3KMA1Y+cTt8LYP3QBc2vANePXVJ+C4D94EX9TYDsR59WuQqg/fATW3WD7cefWqzL/CPjGZABGZABGZABGZABGWAF/El6m0z+AiP1e02uRAyBAAAAAElFTkSuQmCC", "type": "image/png", "sizes": "192x192" }, { - "src": "sebastian-software-pwa-512.png", + "src": "data:image/png;base64,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", "type": "image/png", "sizes": "512x512" }