{"_id":"@cutticat/favicon-fetch","_rev":"2-e2bfaf040e94dd12903117ce1fef5795","name":"@cutticat/favicon-fetch","dist-tags":{"latest":"2.0.4"},"versions":{"2.0.3":{"name":"@cutticat/favicon-fetch","version":"2.0.3","keywords":["favicon","icon","metadata","html","manifest","discovery","cutticat"],"author":{"name":"Maksim Victorovich Fomin"},"license":"MIT","_id":"@cutticat/favicon-fetch@2.0.3","maintainers":[{"name":"maksim2498","email":"maksim@cutticat.com"}],"homepage":"https://cutticat.com","bin":{"favicon-fetch":"dist/bin/fetch.mjs"},"dist":{"shasum":"3de7a36482f57a42f0f87ea2bf2e45cf87b5b9d6","tarball":"https://registry.npmjs.org/@cutticat/favicon-fetch/-/favicon-fetch-2.0.3.tgz","fileCount":8,"integrity":"sha512-A3+fDrCyKPPobBI/rupdhXRoNiuiY1TlrddqceciTOp9V5SyZ0rhmxieGZbOXZWJraSuqnTiX88Ppkroo1+i5Q==","signatures":[{"sig":"MEUCIG0MOuLZtogV/iSJ9+CMQnqFfG11jtuUNlHdhkm7fVEKAiEAoLwqdQtVUMy7NKHhiaM+8FS7lGKgFQJh0tqKfo8bbQk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":120180},"type":"module","_from":"file:cutticat-favicon-fetch-2.0.3.tgz","engines":{"node":">=22.0.0"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}},"scripts":{"lint":"biome check .","test":"tsx --test \"./tests/**/*.test.ts\"","build":"tsc --noEmit -p ./tsconfig.build.json && tsc --noEmit -p ./tsconfig.bin.json && tsdown","clean":"rimraf ./dist","format":"biome format --write .","lint:fix":"biome check --write .","test:watch":"tsx --test --watch \"./tests/**/*.test.ts\"","format:check":"biome format .","favicon:fetch":"tsx --tsconfig ./tsconfig.bin.json ./bin/fetch.ts","test:coverage":"tsx --test --experimental-test-coverage \"./tests/**/*.test.ts\"","lint:fix:unsafe":"biome check --write --unsafe ."},"_npmUser":{"name":"maksim2498","email":"maksim@cutticat.com"},"_resolved":"/tmp/b9d3f18fb171723c2fb001f3bb177c7a/cutticat-favicon-fetch-2.0.3.tgz","_integrity":"sha512-A3+fDrCyKPPobBI/rupdhXRoNiuiY1TlrddqceciTOp9V5SyZ0rhmxieGZbOXZWJraSuqnTiX88Ppkroo1+i5Q==","repository":{"url":"https://gitflic.ru/project/cutticat-npm/favicon-fetch","type":"git"},"_npmVersion":"10.9.4","description":"CuttiCat library for fetching favicons","directories":{},"_nodeVersion":"22.22.0","dependencies":{"cheerio":"^1.2.0","commander":"^15.0.0","image-size":"^2.0.2","@cutticat/types":"^1.7.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.22.4","rimraf":"^6.1.3","tsdown":"0.19.0-beta.2","typescript":"^5.9.3","@types/node":"^25.9.1","@biomejs/biome":"^2.4.16"},"_npmOperationalInternal":{"tmp":"tmp/favicon-fetch_2.0.3_1783456878973_0.036909325525847914","host":"s3://npm-registry-packages-npm-production"}},"2.0.4":{"name":"@cutticat/favicon-fetch","publishConfig":{"access":"public"},"version":"2.0.4","description":"CuttiCat library for fetching favicons","author":{"name":"Maksim Victorovich Fomin"},"repository":{"type":"git","url":"https://gitflic.ru/project/cutticat-npm/favicon-fetch"},"homepage":"https://cutticat.com","keywords":["favicon","icon","metadata","html","manifest","discovery","cutticat"],"type":"module","engines":{"node":">=22.0.0"},"dependencies":{"@cutticat/types":"^1.7.2","cheerio":"^1.2.0","commander":"^15.0.0","image-size":"^2.0.2"},"devDependencies":{"@biomejs/biome":"^2.4.16","@cutticat/md-url-map":"^1.0.0","@types/node":"^25.9.1","rimraf":"^6.1.3","tsdown":"0.19.0-beta.2","tsx":"^4.22.4","typescript":"^5.9.3"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}},"license":"MIT","bin":{"favicon-fetch":"dist/bin/fetch.mjs"},"scripts":{"lint":"biome check .","lint:fix":"biome check --write .","lint:fix:unsafe":"biome check --write --unsafe .","format":"biome format --write .","format:check":"biome format .","clean":"rimraf ./dist","build":"tsc --noEmit -p ./tsconfig.build.json && tsc --noEmit -p ./tsconfig.bin.json && tsdown","favicon:fetch":"tsx --tsconfig ./tsconfig.bin.json ./bin/fetch.ts","test":"tsx --test \"./tests/**/*.test.ts\"","test:watch":"tsx --test --watch \"./tests/**/*.test.ts\"","test:coverage":"tsx --test --experimental-test-coverage \"./tests/**/*.test.ts\""},"_id":"@cutticat/favicon-fetch@2.0.4","_integrity":"sha512-Ypdj0qYGE7y9t7DTBAAG6+B7khQHb7CLFyV2cFMTgenWo2kIzGnsReoTDyd+JVy82nT4jLFP8wfFVMEhpAffyw==","_resolved":"/tmp/c24afd4530e61419b63e67d80a14b291/cutticat-favicon-fetch-2.0.4.tgz","_from":"file:cutticat-favicon-fetch-2.0.4.tgz","_nodeVersion":"22.22.0","_npmVersion":"10.9.4","dist":{"integrity":"sha512-Ypdj0qYGE7y9t7DTBAAG6+B7khQHb7CLFyV2cFMTgenWo2kIzGnsReoTDyd+JVy82nT4jLFP8wfFVMEhpAffyw==","shasum":"2dec51d2eacd353ee4ee783162990a3c270b5847","tarball":"https://registry.npmjs.org/@cutticat/favicon-fetch/-/favicon-fetch-2.0.4.tgz","fileCount":8,"unpackedSize":120560,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDJLC8NLW1/ZDyWvCm04PY49cgseDJhMF/O8+2wn6BPwgIhAMloLAQMzwZGhfyMpHMPR3cKARHdhXHyQwSCxPi2X3PQ"}]},"_npmUser":{"name":"maksim2498","email":"maksim@cutticat.com"},"directories":{},"maintainers":[{"name":"maksim2498","email":"maksim@cutticat.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/favicon-fetch_2.0.4_1789048673310_0.3655024051614624"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-07T20:41:18.786Z","modified":"2026-09-10T13:57:53.694Z","2.0.3":"2026-07-07T20:41:19.207Z","2.0.4":"2026-09-10T13:57:53.439Z"},"author":{"name":"Maksim Victorovich Fomin"},"license":"MIT","homepage":"https://cutticat.com","keywords":["favicon","icon","metadata","html","manifest","discovery","cutticat"],"repository":{"type":"git","url":"https://gitflic.ru/project/cutticat-npm/favicon-fetch"},"description":"CuttiCat library for fetching favicons","maintainers":[{"name":"maksim2498","email":"maksim@cutticat.com"}],"readme":"# @cutticat/favicon-fetch\n\nFavicon discovery and metadata library.\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Installation](#installation)\n- [CLI](#cli)\n- [Quick Start](#quick-start)\n- [API](#api)\n- [Scan Options](#scan-options)\n- [Probe Diagnostics](#probe-diagnostics)\n- [Supported URLs](#supported-urls)\n- [Documentation](#documentation)\n- [Requirements](#requirements)\n\n## Overview\n\n`@cutticat/favicon-fetch` finds favicon candidates on a page and enriches them with metadata:\n\n- `<link rel=\"icon\">`, `apple-touch-icon`, `mask-icon`, and others\n- icons from Web App Manifest (`<link rel=\"manifest\">`)\n- well-known paths on the origin (`/favicon.ico`, `/favicon.png`, …)\n\nThe library **does not download icon bytes** through the API, **does not perform SSRF checks**, and\n**does not cache** — that is the caller's responsibility (for example, the CuttiCat backend). Use\nthe [CLI](#cli) for downloading and debugging.\n\nAfter scanning, use `pickFavicon` to choose the best candidate from the list.\n\nHTTP probe errors are **not thrown** — they are passed to the `onProbeFailure` callback. To abort\nthe scan, throw from the callback.\n\n## Installation\n\n```bash\npnpm i @cutticat/favicon-fetch\n```\n\n## CLI\n\nThe package ships the `favicon-fetch` command (`bin` field). In the library repository, use the\n`favicon:fetch` script (`fetch` is taken by pnpm's built-in command).\n\n```bash\n# In the @cutticat/favicon-fetch repository (after pnpm run build)\npnpm favicon:fetch --list https://github.com\npnpm favicon:fetch https://github.com\npnpm favicon:fetch https://github.com ./favicon.svg\n\n# In a project with @cutticat/favicon-fetch as a dependency\nfavicon-fetch --list https://github.com\nfavicon-fetch https://github.com ./icon.png\n```\n\n| Mode | Behavior |\n| :---: | :---: |\n| `--list` | JSON array of `FaviconMetadata[]` (all discovered candidates) |\n| no flags | `pickFavicon` → download the best icon; JSON `{ output, href, bytes }` |\n\nThe second argument is the output file path. By default, the filename from the icon URL is used (for\nexample, `favicon.ico`, `favicon.svg`).\n\n## Quick Start\n\n```typescript\nimport { pickFavicon, scanFaviconsFromUrl } from \"@cutticat/favicon-fetch\"\n\nconst favicons = await scanFaviconsFromUrl(\"https://example.com\", {\n  // HTML + manifest discovery only, no synthetic paths or HEAD probe\n  scanPaths: [],\n  probe: [],\n  mimeTypeFetchPolicy: \"never\",\n  sizesFetchPolicy: \"never\",\n})\n\nconst best = pickFavicon(favicons)\nconsole.log(best?.href) // https://example.com/favicon.png\n```\n\nFull page URL scan (HTML + manifest + synthetic paths + HEAD probe for synthetic):\n\n```typescript\nimport { pickFavicon, scanFaviconsFromUrl } from \"@cutticat/favicon-fetch\"\n\nconst favicons = await scanFaviconsFromUrl(\"https://example.com\")\nconst best = pickFavicon(favicons)\n```\n\nScanning already-fetched HTML:\n\n```typescript\nimport { pickFavicon, scanFaviconsFromHtml } from \"@cutticat/favicon-fetch\"\n\nconst favicons = await scanFaviconsFromHtml(html, {\n  pageUrl: \"https://example.com/page\",\n})\nconst best = pickFavicon(favicons)\n```\n\n## API\n\n### Scanning (high level)\n\n| Function | Description |\n| :---: | :---: |\n| `scanFaviconsFromUrl(url, options?)` | GET page HTML → scan link + manifest + synthetic paths |\n| `scanFaviconsFromHtml(html, options?)` | Scan from an HTML string |\n| `scanFaviconsFromManifest(manifest, options?)` | Scan from manifest JSON |\n| `scanFaviconsFromManifestUrl(url, options?)` | GET manifest → scan `icons[]` |\n\n### Attribute extraction (without enrich)\n\n| Function | Description |\n| :---: | :---: |\n| `faviconAttributesFromHtml(html, options?)` | `<link>` + manifest icons → `FaviconAttributes[]` |\n| `faviconAttributesFromManifest(manifest)` | `icons[]` → `FaviconAttributes[]` |\n| `faviconAttributesFromManifestUrl(url, options?)` | GET manifest → attributes |\n\n### Enrichment and selection\n\n| Function | Description |\n| :---: | :---: |\n| `scanFaviconsFromAttributes(attrs, options?)` | Resolve URL, dedup, mime/sizes probe, HEAD existence probe |\n| `pickFavicon(favicons)` | Best candidate from `FaviconMetadata[]` |\n| `faviconPriority(meta)` | Numeric priority (for custom sorting) |\n\n### Types\n\n- `FaviconMetadata` — `href`, `rel`, optionally `source`, `mimeType`, `width`, `height`\n- `FaviconAttributes` — raw attributes before scan\n- `FaviconSource` — `\"html\"` \\| `\"manifest\"` \\| `\"synthetic\"`\n- `FetchPolicy` — `\"always\"` \\| `\"never\"` \\| `\"if-not-specified\"`\n- `FaviconProbeKind` — `\"page\"` \\| `\"manifest\"` \\| `\"mime-type\"` \\| `\"dimensions\"` \\| `\"existence\"`\n- `FaviconProbeFailureReason` — `\"http-error\"` \\| `\"network-error\"` \\| `\"bad-content-type\"` \\|\n  `\"bad-payload\"`\n- `FaviconProbeFailure` — failed probe event\n- `OnProbeFailure` — `(failure: FaviconProbeFailure) => void`\n\n### Constants\n\n- `defaultRelValues` — rel tokens for HTML link\n- `defaultScanPaths` — well-known paths for `scanFaviconsFromUrl`\n- `defaultProbeSources` — `[\"synthetic\"]` — sources for HEAD existence probe\n- `allProbeSources` — `[\"html\", \"manifest\", \"synthetic\"]`\n- `manifestIconRel` — `\"manifest\"` for icons from JSON\n- `imageProbeMaxBytes` — Range GET limit when determining dimensions (64 KiB)\n\n## Scan Options\n\n| Option | Default | Purpose |\n| :---: | :---: | :---: |\n| `fetch` | `globalThis.fetch` | HTTP client |\n| `pageUrl` | page URL in `scanFaviconsFromUrl` | Base for HTML `<link href>` and `FaviconProbeFailure.pageUrl` |\n| `manifestUrl` | manifest URL in `scanFaviconsFromManifestUrl` | Base for relative `src` in manifest icons |\n| `onProbeFailure` | — | Callback on failed HTTP probe |\n| `relValues` | `defaultRelValues` | Which rel values from HTML to accept |\n| `mimeTypeFetchPolicy` | `if-not-specified` | HEAD for `content-type` |\n| `sizesFetchPolicy` | `if-not-specified` | Range GET for width/height |\n| `probe` | `[\"synthetic\"]` | HEAD existence filter by `source` |\n| `scanPaths` | `defaultScanPaths` | Synthetic paths in `scanFaviconsFromUrl` |\n| `seenHrefs` | `new Set()` | Dedup across calls |\n| `noDeduplication` | `false` | Disable dedup by href |\n| `cheerio` | — | `cheerio.load` options in `scanFaviconsFromHtml` |\n\nWhen scanning from HTML, manifest icons are resolved relative to the manifest URL automatically. For\n`scanFaviconsFromManifest`, pass `manifestUrl` explicitly.\n\n## Probe Diagnostics\n\n```typescript\nimport { scanFaviconsFromUrl, type FaviconProbeFailure } from \"@cutticat/favicon-fetch\"\n\nconst failures: FaviconProbeFailure[] = []\n\nawait scanFaviconsFromUrl(\"https://example.com\", {\n  onProbeFailure(failure) {\n    failures.push(failure)\n    // throw failure — to abort the scan\n  },\n})\n```\n\n`FaviconProbeFailure` fields:\n\n| Field | Description |\n| :---: | :---: |\n| `kind` | Probe kind: page, manifest, mime, dimensions, existence |\n| `url` | Request URL |\n| `method` | `GET` or `HEAD` |\n| `reason` | Failure reason |\n| `pageUrl` | Source page URL for the scan |\n| `response` | HTTP response, if the request reached the server |\n| `error` | Network or parsing error |\n| `candidate` | Candidate during existence probe |\n| `dropped` | `true` if the candidate was filtered out (existence HEAD) |\n\n## Supported URLs\n\nAllowed:\n\n- `http:` and `https:` (absolute URLs)\n- relative paths (`/favicon.ico`, `icons/a.png`) — HTML via `pageUrl`, manifest `src` via\n  `manifestUrl`\n- protocol-relative (`//cdn.example.com/icon.png`) — resolved via `pageUrl` or `manifestUrl`\n\nAbsolute URLs with any other scheme (`data:`, `file:`, `javascript:`, `ftp:`, etc.) are discarded.\n\n## Documentation\n\n- [Project structure](<https://gitflic.ru/project/cutticat-npm/favicon-fetch/blob?branch=main&file=documents/STRUCTURE.md>)\n- [Scripts](<https://gitflic.ru/project/cutticat-npm/favicon-fetch/blob?branch=main&file=documents/SCRIPTS.md>)\n- [Style guide](<https://gitflic.ru/project/cutticat-npm/favicon-fetch/blob?branch=main&file=documents/STYLE_GUIDE.md>)\n- [Contributing](<https://gitflic.ru/project/cutticat-npm/favicon-fetch/blob?branch=main&file=documents/CONTRIBUTING.md>)\n\nFull API documentation is in source JSDoc comments and in `dist/index.d.ts`.\n\n## Requirements\n\n- Node.js >= 22.0.0\n- pnpm >= 10.17.0\n- TypeScript >= 5.9.0\n","readmeFilename":"README.md"}