'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); const stream$1 = require('stream'); const path = require('path'); const promises = require('fs/promises'); const sharp = require('sharp'); const escapeHtml = require('escape-html'); const xml2js = require('xml2js'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } const sharp__default = /*#__PURE__*/_interopDefaultCompat(sharp); const escapeHtml__default = /*#__PURE__*/_interopDefaultCompat(escapeHtml); const xml2js__default = /*#__PURE__*/_interopDefaultCompat(xml2js); const defaultOptions = { path: "/", appName: null, appShortName: null, appDescription: null, developerName: null, developerURL: null, cacheBustingQueryParam: null, dir: "auto", lang: "en-US", background: "#fff", theme_color: "#fff", appleStatusBarStyle: "black-translucent", display: "standalone", orientation: "any", start_url: "/?homescreen=1", version: "1.0", pixel_art: false, loadManifestWithCredentials: false, manifestRelativePaths: false, manifestMaskable: false, preferRelatedApplications: false, icons: { android: true, appleIcon: true, appleStartup: true, favicons: true, windows: true, yandex: true }, output: { images: true, files: true, html: true } }; const HEADER_SIZE = 6; const DIRECTORY_SIZE = 16; const COLOR_MODE = 0; const BITMAP_SIZE = 40; function createHeader(n) { const buf = Buffer.alloc(HEADER_SIZE); buf.writeUInt16LE(0, 0); buf.writeUInt16LE(1, 2); buf.writeUInt16LE(n, 4); return buf; } function createDirectory(image, offset) { const buf = Buffer.alloc(DIRECTORY_SIZE); const { width, height } = image.info; const size = width * height * 4 + BITMAP_SIZE; const bpp = 32; buf.writeUInt8(width === 256 ? 0 : width, 0); buf.writeUInt8(height === 256 ? 0 : height, 1); buf.writeUInt8(0, 2); buf.writeUInt8(0, 3); buf.writeUInt16LE(1, 4); buf.writeUInt16LE(bpp, 6); buf.writeUInt32LE(size, 8); buf.writeUInt32LE(offset, 12); return buf; } function createBitmap(image, compression) { const buf = Buffer.alloc(BITMAP_SIZE); const { width, height } = image.info; buf.writeUInt32LE(BITMAP_SIZE, 0); buf.writeInt32LE(width, 4); buf.writeInt32LE(height * 2, 8); buf.writeUInt16LE(1, 12); buf.writeUInt16LE(32, 14); buf.writeUInt32LE(compression, 16); buf.writeUInt32LE(width * height, 20); buf.writeInt32LE(0, 24); buf.writeInt32LE(0, 28); buf.writeUInt32LE(0, 32); buf.writeUInt32LE(0, 36); return buf; } function createDib(image) { const { width, height } = image.info; const imageData = image.data; const buf = Buffer.alloc(width * height * 4); for (let y = 0; y < height; ++y) { for (let x = 0; x < height; ++x) { const offset = (y * width + x) * 4; const r = imageData.readUInt8(offset); const g = imageData.readUInt8(offset + 1); const b = imageData.readUInt8(offset + 2); const a = imageData.readUInt8(offset + 3); const pos = (height - y - 1) * width + x; buf.writeUInt8(b, pos * 4); buf.writeUInt8(g, pos * 4 + 1); buf.writeUInt8(r, pos * 4 + 2); buf.writeUInt8(a, pos * 4 + 3); } } return buf; } function toIco(images) { const header = createHeader(images.length); let arr = [header]; let offset = HEADER_SIZE + DIRECTORY_SIZE * images.length; const bitmaps = images.map((image) => { const bitmapHeader = createBitmap(image, COLOR_MODE); const dib = createDib(image); return Buffer.concat([bitmapHeader, dib]); }); for (let i = 0; i < images.length; ++i) { const image = images[i]; const bitmap = bitmaps[i]; const dir = createDirectory(image, offset); arr.push(dir); offset += bitmap.length; } arr = [...arr, ...bitmaps]; return Buffer.concat(arr); } function svgDensity(metadata, width, height) { if (!metadata.width || !metadata.height) { return void 0; } const currentDensity = metadata.density ?? 72; return Math.min( Math.max( 1, currentDensity, currentDensity * width / metadata.width, currentDensity * height / metadata.height ), 1e5 ); } function arrayComparator(a, b) { const aArr = [a].flat(Infinity); const bArr = [b].flat(Infinity); for (let i = 0; i < Math.max(aArr.length, bArr.length); ++i) { if (i >= aArr.length) return -1; if (i >= bArr.length) return 1; if (aArr[i] !== bArr[i]) { return aArr[i] < bArr[i] ? -1 : 1; } } return 0; } function minBy(array, comparator) { return array.reduce((acc, cur) => comparator(acc, cur) < 0 ? acc : cur); } function minByKey(array, keyFn) { return minBy(array, (a, b) => arrayComparator(keyFn(a), keyFn(b))); } function asString(arg) { return typeof arg === "string" || arg instanceof String ? arg.toString() : void 0; } async function sourceImages(src) { if (Buffer.isBuffer(src)) { try { return [ { data: src, metadata: await sharp__default(src).metadata() } ]; } catch (error) { return Promise.reject(new Error("Invalid image buffer")); } } else if (typeof src === "string") { const buffer = await promises.readFile(src); return await sourceImages(buffer); } else if (Array.isArray(src) && !src.some(Array.isArray)) { if (!src.length) { throw new Error("No source provided"); } const images = await Promise.all(src.map(sourceImages)); return images.flat(); } else { throw new Error("Invalid source type provided"); } } function flattenIconOptions(iconOptions) { return iconOptions.sizes.map((size) => ({ ...size, offset: iconOptions.offset ?? 0, pixelArt: iconOptions.pixelArt ?? false, background: asString(iconOptions.background), transparent: iconOptions.transparent, rotate: iconOptions.rotate })); } function relativeTo(base, path) { if (!base) { return path; } const directory = base.endsWith("/") ? base : `${base}/`; const url = new URL(path, new URL(directory, "resolve://")); return url.protocol === "resolve:" ? url.pathname : url.toString(); } function bestSource(sourceset, width, height) { const sideSize = Math.max(width, height); return minByKey(sourceset, (icon) => { const iconSideSize = Math.max(icon.metadata.width, icon.metadata.height); return [ icon.metadata.format === "svg" ? 0 : 1, // prefer SVG iconSideSize >= sideSize ? 0 : 1, // prefer downscale Math.abs(iconSideSize - sideSize) // prefer closest size ]; }); } async function resize(source, width, height, pixelArt) { if (source.metadata.format === "svg") { const options = { density: svgDensity(source.metadata, width, height) }; return await sharp__default(source.data, options).resize({ width, height, fit: sharp__default.fit.contain, background: "#00000000" }).toBuffer(); } else { return await sharp__default(source.data).ensureAlpha().resize({ width, height, fit: sharp__default.fit.contain, background: "#00000000", kernel: pixelArt && width >= source.metadata.width && height >= source.metadata.height ? "nearest" : "lanczos3" }).toBuffer(); } } function createBlankImage(width, height, background) { const transparent = !background || background === "transparent"; let image = sharp__default({ create: { width, height, channels: transparent ? 4 : 3, background: transparent ? "#00000000" : background } }); if (transparent) { image = image.ensureAlpha(); } return image; } async function createPlane(sourceset, options) { const offset = Math.round( Math.max(options.width, options.height) * options.offset / 100 ) || 0; const width = options.width - offset * 2; const height = options.height - offset * 2; const source = bestSource(sourceset, width, height); const image = await resize(source, width, height, options.pixelArt); let pipeline = createBlankImage( options.width, options.height, options.background ).composite([{ input: image, left: offset, top: offset }]); if (options.rotate) { const degrees = 90; pipeline = pipeline.rotate(degrees); } return pipeline; } function toRawImage(pipeline) { return pipeline.toColorspace("srgb").raw({ depth: "uchar" }).toBuffer({ resolveWithObject: true }); } function toDarkModeRawImage(pipeline) { return pipeline.toColorspace("srgb").raw({ depth: "uchar" }).linear([255, 255, 255], [255, 255, 255]).toBuffer({ resolveWithObject: true }); } function toPng(pipeline) { return pipeline.png().toBuffer(); } function toDarkModePng(pipeline) { return pipeline.png().linear([255, 255, 255], [255, 255, 255]).toBuffer(); } async function createSvg(sourceset, options) { const { width, height } = options; const source = bestSource(sourceset, width, height); if (source.metadata.format === "svg") { return source.data; } else { const pipeline = await createPlane(sourceset, options); const png = await toPng(pipeline); const encodedPng = png.toString("base64"); return Buffer.from( ` ` ); } } async function createFavicon(sourceset, name, iconOptions) { const properties = flattenIconOptions(iconOptions); const ext = path.extname(name); if (ext === ".ico" || properties.length !== 1) { const images = await Promise.all( properties.map( (props) => createPlane(sourceset, props).then( name.startsWith("favicon") && name.endsWith("-dark.ico") ? toDarkModeRawImage : toRawImage ) ) ); const contents = toIco(images); return { name, contents }; } else if (ext === ".svg") { const contents = await createSvg(sourceset, properties[0]); return { name, contents }; } else { const contents = await createPlane(sourceset, properties[0]).then( name.startsWith("favicon") && name.endsWith("-dark.png") ? toDarkModePng : toPng ); return { name, contents }; } } async function createScreenshot(sourceset, name, format) { const { width, height } = sourceset[0].metadata; const contents = await sharp__default(sourceset[0].data).resize(width, height).toFormat(format).toBuffer(); return { name, contents }; } function transparentIcon(width, height) { return { sizes: [{ width, height: height ?? width }], offset: 0, background: false, transparent: true, rotate: false }; } function transparentIcons(...sizes) { return { sizes: sizes.map((size) => ({ width: size, height: size })), offset: 0, background: false, transparent: true, rotate: false }; } function opaqueIcon(width, height) { return { sizes: [{ width, height: height ?? width }], offset: 0, background: true, transparent: false, rotate: false }; } function maskable(options) { return { ...options, purpose: "maskable" }; } function uniformIconOptions(options, iconsChoice, platformConfig) { let result = []; if (Array.isArray(iconsChoice)) { const iconsChoices = Object.fromEntries( iconsChoice.map( (choice) => typeof choice === "object" ? [choice.name, choice] : [choice, { name: choice }] ) ); result = platformConfig.filter((iconOptions) => iconOptions.name in iconsChoices).map((iconOptions) => ({ ...iconOptions, ...iconsChoices[iconOptions.name] })); } else if (typeof iconsChoice === "object") { result = platformConfig.filter((iconOptions) => !iconOptions.optional).map((iconOptions) => ({ ...iconOptions, ...iconsChoice })); } else { result = platformConfig.filter((iconOptions) => !iconOptions.optional); } return result.map((iconOptions) => ({ pixelArt: options.pixel_art, ...iconOptions, background: iconOptions.background === true ? options.background : asString(iconOptions.background) })); } class Platform { constructor(options, iconOptions) { this.options = options; this.iconOptions = iconOptions; } async create(sourceset) { const { output } = this.options; return { images: output.images ? await this.createImages(sourceset) : [], files: output.files ? await this.createFiles() : [], html: output.html ? await this.createHtml() : [] }; } async createImages(sourceset) { return await Promise.all( this.iconOptions.map( (iconOption) => createFavicon(sourceset, iconOption.name, iconOption) ) ); } async createFiles() { return []; } async createHtml() { return []; } relative(path) { return relativeTo(this.options.path, path); } cacheBusting(path) { if (typeof this.options.cacheBustingQueryParam !== "string") { return path; } const paramParts = this.options.cacheBustingQueryParam.split("="); if (paramParts.length === 1) { return path; } const url = new URL(path, "https://cache.busting"); url.searchParams.set(paramParts[0], paramParts.slice(1).join("=")); return url.origin === "https://cache.busting" ? url.pathname + url.search : url.toString(); } } const ICONS_OPTIONS$5 = [ { name: "android-chrome-36x36.png", ...transparentIcon(36) }, { name: "android-chrome-48x48.png", ...transparentIcon(48) }, { name: "android-chrome-72x72.png", ...transparentIcon(72) }, { name: "android-chrome-96x96.png", ...transparentIcon(96) }, { name: "android-chrome-144x144.png", ...transparentIcon(144) }, { name: "android-chrome-192x192.png", ...transparentIcon(192) }, { name: "android-chrome-256x256.png", ...transparentIcon(256) }, { name: "android-chrome-384x384.png", ...transparentIcon(384) }, { name: "android-chrome-512x512.png", ...transparentIcon(512) } ]; const ICONS_OPTIONS_MASKABLE = [ { name: "android-chrome-maskable-36x36.png", ...maskable(transparentIcon(36)) }, { name: "android-chrome-maskable-48x48.png", ...maskable(transparentIcon(48)) }, { name: "android-chrome-maskable-72x72.png", ...maskable(transparentIcon(72)) }, { name: "android-chrome-maskable-96x96.png", ...maskable(transparentIcon(96)) }, { name: "android-chrome-maskable-144x144.png", ...maskable(transparentIcon(144)) }, { name: "android-chrome-maskable-192x192.png", ...maskable(transparentIcon(192)) }, { name: "android-chrome-maskable-256x256.png", ...maskable(transparentIcon(256)) }, { name: "android-chrome-maskable-384x384.png", ...maskable(transparentIcon(384)) }, { name: "android-chrome-maskable-512x512.png", ...maskable(transparentIcon(512)) } ]; const SHORTCUT_ICONS_OPTIONS = { "36x36.png": transparentIcon(36), "48x48.png": transparentIcon(48), "72x72.png": transparentIcon(72), "96x96.png": transparentIcon(96), "144x144.png": transparentIcon(144), "192x192.png": transparentIcon(192) }; class AndroidPlatform extends Platform { constructor(options) { super( options, uniformIconOptions(options, options.icons.android, ICONS_OPTIONS$5) ); } async createImages(sourceset) { let icons = await Promise.all( this.iconOptions.map( (iconOption) => createFavicon(sourceset, iconOption.name, iconOption) ) ); if (this.options.manifestMaskable && typeof this.options.manifestMaskable !== "boolean") { const maskableSourceset = await sourceImages( this.options.manifestMaskable ); const maskableIcons = await Promise.all( ICONS_OPTIONS_MASKABLE.map( (iconOption) => createFavicon(maskableSourceset, iconOption.name, iconOption) ) ); icons = [...icons, ...maskableIcons]; } if (Array.isArray(this.options.shortcuts) && this.options.shortcuts.length > 0) { icons = [...icons, ...await this.shortcutIcons()]; } if (this.options.screenshots) { icons = [...icons, ...await this.screenshots()]; } return icons; } async createFiles() { return [await this.manifest()]; } async createHtml() { return [ this.options.loadManifestWithCredentials ? `` : ``, ``, ``, this.options.appName ? `` : `` ]; } async shortcutIcons() { const icons = await Promise.all( this.options.shortcuts.map(async (shortcut, index) => { if (!shortcut.name || !shortcut.url || !shortcut.icon) return null; const shortcutSourceset = await sourceImages(shortcut.icon); return Promise.all( Object.entries(SHORTCUT_ICONS_OPTIONS).map( ([shortcutName, option]) => createFavicon( shortcutSourceset, `shortcut${index + 1}-${shortcutName}`, option ) ) ); }) ); return icons.flat(); } async screenshots() { const screenshots = await Promise.all( this.options.screenshots.map(async (screenshot, index) => { if (!screenshot.image) return null; const screenshotSourceset = await sourceImages(screenshot.image); const { width, height } = screenshotSourceset[0].metadata; const { platform, format } = screenshot; const Format = format || "webp"; const prefix = platform ? `${platform}-` : ""; const screenshotImages = await createScreenshot( screenshotSourceset, // prettier-ignore `${prefix}screenshots${index + 1}-${width}x${height}.${Format}`, Format ); return screenshotImages; }) ); return screenshots.flat().filter((screenshot) => screenshot !== null); } manifestFileName() { return this.options.files?.android?.manifestFileName ?? "manifest.webmanifest"; } async manifest() { const { options } = this; const basePath = options.manifestRelativePaths ? null : options.path; const properties = { name: options.appName, name_localized: options.name_localized, short_name: options.appShortName || options.appName, categories: options.appCategories, description: options.appDescription, dir: options.dir, lang: options.lang, display: options.display, display_override: options.display_override, file_handlers: options.file_handlers, orientation: options.orientation, protocol_handlers: options.protocol_handlers, scope: options.scope, id: options.id, share_target: options.share_target, start_url: options.start_url, background_color: options.background, theme_color: options.theme_color }; if (options.preferRelatedApplications) { properties.prefer_related_applications = options.preferRelatedApplications; } if (Array.isArray(options.relatedApplications) && options.relatedApplications.length > 0) { properties.related_applications = options.relatedApplications; } let icons = this.iconOptions; if (options.manifestMaskable && typeof options.manifestMaskable !== "boolean") { icons = [...icons, ...ICONS_OPTIONS_MASKABLE]; } const defaultPurpose = options.manifestMaskable === true ? "maskable" : "any"; properties.icons = icons.map((iconOptions) => { const { width, height } = iconOptions.sizes[0]; return { src: this.cacheBusting(relativeTo(basePath, iconOptions.name)), sizes: `${width}x${height}`, type: "image/png", purpose: iconOptions.purpose ?? (width === 512 ? "maskable" : defaultPurpose) // 自动添加 any 以排除至少需要一个 any 的警告 }; }); if (Array.isArray(options.shortcuts) && options.shortcuts.length > 0) { properties.shortcuts = this.manifestShortcuts(basePath); } if (Array.isArray(options.screenshots) && options.screenshots.length > 0) { properties.screenshots = await this.manifestScreenshots(basePath); } return { name: this.manifestFileName(), contents: JSON.stringify(properties, null, 2) }; } manifestShortcuts(basePath) { return this.options.shortcuts.map((shortcut, index) => { if (!shortcut.name || !shortcut.url) return null; return { name: shortcut.name, short_name: shortcut.short_name || shortcut.name, // fallback to name description: shortcut.description, // optional url: shortcut.url, icons: shortcut.icon ? Object.entries(SHORTCUT_ICONS_OPTIONS).map( ([shortcutName, option]) => { const { width, height } = option.sizes[0]; return { src: this.cacheBusting( relativeTo( basePath, `shortcut${index + 1}-${shortcutName}` ) ), sizes: `${width}x${height}`, type: "image/png" }; } ) : void 0 }; }).filter((x) => x !== null); } async manifestScreenshots(basePath) { const screenshots = await Promise.all( this.options.screenshots.map(async (screenshot, index) => { const { form_factor, label, platform, image, format } = screenshot; try { const screenshotSourceset = await sourceImages(image); if (screenshotSourceset.length === 0) { throw new Error("No valid source images found"); } const { width, height } = screenshotSourceset[0].metadata; const calculatedFormFactor = form_factor || (width < 720 ? "narrow" : "wide"); const prefix = platform ? `${platform}-` : ""; const screenshotImageSrc = this.cacheBusting( relativeTo( basePath, // prettier-ignore `${prefix}screenshots${index + 1}-${width}x${height}.${format || "webp"}` ) ); return { src: screenshotImageSrc, sizes: `${width}x${height}`, type: `image/${format || "webp"}`, form_factor: calculatedFormFactor, label, platform }; } catch (error) { console.error(`Error processing screenshot ${index + 1}:`, error); return null; } }) ); return screenshots.filter((screenshot) => screenshot !== null); } } const ICONS_OPTIONS$4 = [ { name: "apple-touch-icon-57x57.png", ...opaqueIcon(57) }, { name: "apple-touch-icon-60x60.png", ...opaqueIcon(60) }, { name: "apple-touch-icon-72x72.png", ...opaqueIcon(72) }, { name: "apple-touch-icon-76x76.png", ...opaqueIcon(76) }, { name: "apple-touch-icon-114x114.png", ...opaqueIcon(114) }, { name: "apple-touch-icon-120x120.png", ...opaqueIcon(120) }, { name: "apple-touch-icon-144x144.png", ...opaqueIcon(144) }, { name: "apple-touch-icon-152x152.png", ...opaqueIcon(152) }, { name: "apple-touch-icon-167x167.png", ...opaqueIcon(167) }, { name: "apple-touch-icon-180x180.png", ...opaqueIcon(180) }, { name: "apple-touch-icon-1024x1024.png", ...opaqueIcon(1024) }, { name: "apple-touch-icon-precomposed.png", ...opaqueIcon(180) }, { name: "apple-touch-icon.png", ...opaqueIcon(180) }, { name: "safari-pinned-tab.svg", ...opaqueIcon(16) } ]; class AppleIconPlatform extends Platform { constructor(options) { super( options, uniformIconOptions(options, options.icons.appleIcon, ICONS_OPTIONS$4) ); } async createHtml() { const icons = this.iconOptions.map((options) => { const { width, height } = options.sizes[0]; const { name: name2 } = options; if (name2.endsWith(".svg")) { return ``; } return ``; }); const name = this.options.appShortName || this.options.appName; return [ ...icons, ``, ``, name ? `` : `` ]; } } const SCREEN_SIZES = [ { deviceWidth: 320, deviceHeight: 568, pixelRatio: 2 }, // 4" iPhone SE, iPod touch 5th generation and later { deviceWidth: 375, deviceHeight: 667, pixelRatio: 2 }, // iPhone 8, iPhone 7, iPhone 6s, iPhone 6, 4.7" iPhone SE { deviceWidth: 375, deviceHeight: 812, pixelRatio: 3 }, // iPhone 12 mini, iPhone 11 Pro, iPhone XS, iPhone X { deviceWidth: 390, deviceHeight: 844, pixelRatio: 3 }, // iPhone 12, iPhone 12 Pro { deviceWidth: 393, deviceHeight: 852, pixelRatio: 3 }, // iPhone 14 Pro, iPhone 15 Pro, iPhone 15 { deviceWidth: 414, deviceHeight: 896, pixelRatio: 2 }, // iPhone 11, iPhone XR { deviceWidth: 414, deviceHeight: 896, pixelRatio: 3 }, // iPhone 11 Pro Max, iPhone XS Max { deviceWidth: 414, deviceHeight: 736, pixelRatio: 3 }, // iPhone 8 Plus, iPhone 7 Plus { deviceWidth: 414, deviceHeight: 736, pixelRatio: 3 }, // iPhone 6 Plus, iPhone 6s Plus { deviceWidth: 428, deviceHeight: 926, pixelRatio: 3 }, // iPhone 12 Pro Max, iPhone 13 Pro Max, iPhone 14 Plus { deviceWidth: 430, deviceHeight: 932, pixelRatio: 3 }, // iPhone 14 Pro Max, iPhone 15 Pro Max, iPhone 15 Plus { deviceWidth: 744, deviceHeight: 1133, pixelRatio: 2 }, // 8.3" iPad Mini { deviceWidth: 768, deviceHeight: 1024, pixelRatio: 2 }, // 9.7" iPad Pro. 7.9" iPad mini, 9.7" iPad Air, 9.7" iPad { deviceWidth: 810, deviceHeight: 1080, pixelRatio: 2 }, // 10.2" iPad { deviceWidth: 820, deviceHeight: 1080, pixelRatio: 2 }, // 10.9" iPad Air { deviceWidth: 834, deviceHeight: 1194, pixelRatio: 2 }, // 11" iPad Pro, 10.5" iPad Pro { deviceWidth: 834, deviceHeight: 1112, pixelRatio: 2 }, // 10.5" iPad Air { deviceWidth: 1024, deviceHeight: 1366, pixelRatio: 2 } // 12.9" iPad Pro ]; function iconOptions() { const result = {}; for (const size of SCREEN_SIZES) { const pixelWidth = size.deviceWidth * size.pixelRatio; const pixelHeight = size.deviceHeight * size.pixelRatio; const namePortrait = `apple-touch-startup-image-${pixelWidth}x${pixelHeight}.png`; result[namePortrait] = { name: namePortrait, ...opaqueIcon(pixelWidth, pixelHeight), ...size, orientation: "portrait" }; const nameLandscape = `apple-touch-startup-image-${pixelHeight}x${pixelWidth}.png`; result[nameLandscape] = { name: nameLandscape, ...opaqueIcon(pixelHeight, pixelWidth), ...size, orientation: "landscape" }; } return Object.values(result); } const ICONS_OPTIONS$3 = iconOptions(); class AppleStartupPlatform extends Platform { constructor(options) { super( options, uniformIconOptions(options, options.icons.appleStartup, ICONS_OPTIONS$3) ); } async createHtml() { return this.iconOptions.map( (item) => `` ); } } const ICONS_OPTIONS$2 = [ { name: "favicon.ico", ...transparentIcons(16, 24, 32, 48, 64) }, { name: "favicon-16x16.png", ...transparentIcon(16) }, { name: "favicon-32x32.png", ...transparentIcon(32) }, { name: "favicon-48x48.png", ...transparentIcon(48) }, { name: "favicon.svg", ...transparentIcon(1024) } // arbitrary size. if more than one svg source is given, the closest to this size will be picked. ]; const ICONS_OPTIONS_DARKMODE = [ ...ICONS_OPTIONS$2, { name: "favicon-dark.ico", ...transparentIcons(16, 24, 32, 48, 64) }, { name: "favicon-16x16-dark.png", ...transparentIcon(16) }, { name: "favicon-32x32-dark.png", ...transparentIcon(32) }, { name: "favicon-48x48-dark.png", ...transparentIcon(48) } ]; class FaviconsPlatform extends Platform { constructor(options) { super( options, uniformIconOptions( options, options.icons.favicons, options.faviconsDarkMode ? ICONS_OPTIONS_DARKMODE : ICONS_OPTIONS$2 ) ); } async createHtml() { if (this.options.faviconsDarkMode) { return this.iconOptions.map(({ name, ...options }) => { if (name.endsWith(".ico")) { if (name.endsWith("-dark.ico")) { return ``; } else { return ``; } } else if (name.endsWith(".svg")) { return ``; } const { width, height } = options.sizes[0]; if (name.endsWith("-dark.png")) { return ``; } else { return ``; } }); } return this.iconOptions.map(({ name, ...options }) => { if (name.endsWith(".ico")) { return ``; } else if (name.endsWith(".svg")) { return ``; } const { width, height } = options.sizes[0]; return ``; }); } } const ICONS_OPTIONS$1 = [ { name: "mstile-70x70.png", ...transparentIcon(70) }, { name: "mstile-144x144.png", ...transparentIcon(144) }, { name: "mstile-150x150.png", ...transparentIcon(150) }, { name: "mstile-310x150.png", ...transparentIcon(310, 150) }, { name: "mstile-310x310.png", ...transparentIcon(310) } ]; const SUPPORTED_TILES = [ { name: "square70x70logo", width: 70, height: 70 }, { name: "square150x150logo", width: 150, height: 150 }, { name: "wide310x150logo", width: 310, height: 150 }, { name: "square310x310logo", width: 310, height: 310 } ]; function hasSize(size, icon) { return icon.sizes.length === 1 && icon.sizes[0].width === size.width && icon.sizes[0].height === size.height; } class WindowsPlatform extends Platform { constructor(options) { super( options, uniformIconOptions(options, options.icons.windows, ICONS_OPTIONS$1) ); if (!this.options.background) { throw new Error("`background` is required for Windows icons"); } } async createFiles() { return [this.browserConfig()]; } async createHtml() { const tile = "mstile-144x144.png"; return [ ``, this.iconOptions.find((iconOption) => iconOption.name === tile) ? `` : "", `` ]; } manifestFileName() { return this.options.files?.windows?.manifestFileName ?? "browserconfig.xml"; } browserConfig() { const basePath = this.options.manifestRelativePaths ? null : this.options.path; const tile = {}; for (const { name, ...size } of SUPPORTED_TILES) { const icon = this.iconOptions.find( (iconOption) => hasSize(size, iconOption) ); if (icon) { tile[name] = { $: { src: this.cacheBusting(relativeTo(basePath, icon.name)) } }; } } const browserconfig = { browserconfig: { msapplication: { tile: { ...tile, TileColor: { _: this.options.background } } } } }; const contents = new xml2js__default.Builder({ xmldec: { version: "1.0", encoding: "utf-8", standalone: null } }).buildObject(browserconfig); return { name: this.manifestFileName(), contents }; } } const ICONS_OPTIONS = [ { name: "yandex-browser-50x50.png", ...transparentIcon(50) } ]; class YandexPlatform extends Platform { constructor(options) { super( options, uniformIconOptions(options, options.icons.yandex, ICONS_OPTIONS) ); } async createFiles() { return [this.manifest()]; } async createHtml() { return [ `` ]; } manifestFileName() { return this.options.files?.yandex?.manifestFileName ?? "yandex-browser-manifest.json"; } manifest() { const basePath = this.options.manifestRelativePaths ? null : this.options.path; const logo = this.iconOptions[0].name; const properties = { version: this.options.version, api_version: 1, layout: { logo: this.cacheBusting(relativeTo(basePath, logo)), color: this.options.background, show_title: true } }; return { name: this.manifestFileName(), contents: JSON.stringify(properties, null, 2) }; } } function getPlatform(name, options) { switch (name) { case "android": return new AndroidPlatform(options); case "appleIcon": return new AppleIconPlatform(options); case "appleStartup": return new AppleStartupPlatform(options); case "favicons": return new FaviconsPlatform(options); case "windows": return new WindowsPlatform(options); case "yandex": return new YandexPlatform(options); default: throw new Error(`Unsupported platform ${name}`); } } var __accessCheck = (obj, member, msg) => { if (!member.has(obj)) throw TypeError("Cannot " + msg); }; var __privateGet = (obj, member, getter) => { __accessCheck(obj, member, "read from private field"); return getter ? getter.call(obj) : member.get(obj); }; var __privateAdd = (obj, member, value) => { if (member.has(obj)) throw TypeError("Cannot add the same private member more than once"); member instanceof WeakSet ? member.add(obj) : member.set(obj, value); }; var __privateSet = (obj, member, value, setter) => { __accessCheck(obj, member, "write to private field"); setter ? setter.call(obj, value) : member.set(obj, value); return value; }; var __privateMethod = (obj, member, method) => { __accessCheck(obj, member, "access private method"); return method; }; var _options, _handleHTML, _convertContent, convertContent_fn; const config = { defaults: defaultOptions }; async function favicons(source, options = {}) { options = { ...defaultOptions, ...options, icons: { ...defaultOptions.icons, ...options.icons }, output: { ...defaultOptions.output, ...options.output } }; const sourceset = await sourceImages(source); const platforms = Object.keys(options.icons).filter((platform) => options.icons[platform]).sort((a, b) => { if (a === "favicons") return -1; if (b === "favicons") return 1; return a.localeCompare(b); }); const responses = []; for (const platformName of platforms) { const platform = getPlatform(platformName, options); responses.push(await platform.create(sourceset)); } return { images: responses.flatMap((r) => r.images), files: responses.flatMap((r) => r.files), html: responses.flatMap((r) => r.html) }; } class FaviconStream extends stream$1.Transform { constructor(options, handleHTML) { super({ objectMode: true }); __privateAdd(this, _convertContent); __privateAdd(this, _options, void 0); __privateAdd(this, _handleHTML, void 0); __privateSet(this, _options, options); __privateSet(this, _handleHTML, handleHTML); } _transform(file, _encoding, callback) { const { html: htmlPath, pipeHTML, ...options } = __privateGet(this, _options); favicons(file, options).then(({ images, files, html }) => { for (const { name, contents } of [...images, ...files]) { this.push({ name, contents: __privateMethod(this, _convertContent, convertContent_fn).call(this, contents) }); } if (__privateGet(this, _handleHTML)) { __privateGet(this, _handleHTML).call(this, html); } if (pipeHTML) { this.push({ name: htmlPath, contents: __privateMethod(this, _convertContent, convertContent_fn).call(this, html.join("\n")) }); } callback(null); }).catch(callback); } } _options = new WeakMap(); _handleHTML = new WeakMap(); _convertContent = new WeakSet(); convertContent_fn = function(contents) { return (__privateGet(this, _options).emitBuffers ?? true) && !Buffer.isBuffer(contents) ? Buffer.from(contents) : contents; }; function stream(options, handleHTML) { return new FaviconStream(options, handleHTML); } exports.config = config; exports.default = favicons; exports.favicons = favicons; exports.stream = stream;