fontfetch 0.6.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,112 @@
1
+ # Changelog
2
+
3
+ All notable changes to fontfetch will be documented in this file.
4
+
5
+ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.6.0] — 2026-05-27
10
+
11
+ ### Added
12
+ - **Provenance grouping.** Every downloaded font now lives under `files/<bucket>/<name>`, where bucket is one of `google` / `adobe-typekit` / `commercial` / `open-cdn` / `self-hosted`. Same first-match-wins precedence as the license heuristic; same-origin URLs (with `www.` and subdomain handling) fall to `self-hosted`.
13
+ - New module `src/provenance.ts` with `bucketForUrl(url, pageHost)` and `sameOrigin(a, b)` helpers.
14
+ - 18 unit tests for bucketing and same-origin detection.
15
+
16
+ ### Changed
17
+ - **Breaking layout change** (no published consumers): font files moved from `files/*.woff2` to `files/<bucket>/*.woff2`. `fonts.css`, `fonts.json`, `LICENSE_REVIEW.md`, and every framework emitter now reference the bucketed paths automatically.
18
+ - Per-file progress log prefixes the bucket: `✓ google/Inter-Regular.woff2` instead of `✓ Inter-Regular.woff2`.
19
+
20
+ ### Notes
21
+ - v0.5 was originally scoped as a static `preview.html`. We've decided to skip that and roll it into a much larger v0.5 — a hosted Next.js webapp at `fontfetch.dev` with live progress, foundry-style previews, side-by-side compare, and font-pairing. See [docs/roadmap.md](docs/roadmap.md#v05--hosted-webapp) for the public plan.
22
+
23
+ ## [0.4.0] — 2026-05-27
24
+
25
+ ### Added
26
+ - **License heuristic + `LICENSE_REVIEW.md`**. Every pull now classifies each face as `open`, `commercial`, or `unknown` based on a URL-signature heuristic (Adobe Typekit, Monotype `fast.fonts.net`, Hoefler `cloud.typography.com`, Type Network, Adobe Fonts) plus a family-name fallback against a curated SIL OFL / Google Fonts catalog snapshot. Result is written as `LICENSE_REVIEW.md` alongside the rest of the per-site bundle.
27
+ - **Fail-fast on all-commercial sites.** When every detected face is served from a known commercial-foundry CDN, fontfetch aborts before downloading. It still emits `LICENSE_REVIEW.md` so the user can see what was detected, and prints a clear message recommending `--force` if they have a legitimate reason to proceed.
28
+ - **`--force` flag.** Bypasses the fail-fast check. Mirrors `npm install --force` semantics — you're telling the tool "I know what I'm doing."
29
+ - 12 new unit tests for the classifier and summarizer (`test/license.test.ts`).
30
+
31
+ ### Changed
32
+ - CLI summary line at the end of a successful run now shows the license breakdown (`open / commercial / unknown` counts).
33
+
34
+ ### Notes
35
+ - The classifier is heuristic-only and conservative on purpose — false-commercial is a safer failure mode than false-open (which could mislead a user into shipping a paid font).
36
+ - Adding a CDN signature is a one-line change in [src/license-data.ts](src/license-data.ts). PRs welcome.
37
+
38
+ ## [0.3.0] — 2026-05-27
39
+
40
+ ### Added
41
+ - **`--emit <targets>` flag**. Comma-separated framework targets emitted alongside the default `fonts.css`:
42
+ - `next` → `next.fonts.ts` using `next/font/local`, one `localFont` call per family with all weights/styles, plus a CSS variable per family ready to spread into `<html>`
43
+ - `tailwind` → `tailwind.fonts.ts` with `fontFamily` mapped into `sans` / `serif` / `mono` (heuristic) plus per-family aliases. Pairs with `--emit next` for CSS variables
44
+ - `vite` → `vite.fonts.md` with a copy-paste integration guide
45
+ - `css` → default (no-op flag; just makes the default explicit)
46
+ - Multiple targets allowed: `--emit next,tailwind` emits both
47
+ - `--emit=next,tailwind` (equals form) also accepted
48
+ - New `src/emitters/` module with one file per target, a shared `util.ts`, and a typed `Emitter` interface
49
+ - Vitest test harness with unit tests for every emitter and the utility helpers
50
+ - CI now runs `npm run test` between typecheck and build
51
+
52
+ ### Changed
53
+ - `tsconfig.json` now includes `test/**/*` so the test files are typechecked alongside source
54
+ - Bumped to v0.3.0
55
+
56
+ ## [0.2.2] — 2026-05-27
57
+
58
+ ### Changed
59
+ - **Referer-aware font downloads.** Every font request now sends a `Referer` header set to the originating page URL (the same header browsers send automatically when loading subresources). Many foundry CDNs and some self-hosted setups return 403 without it. Mirrors what we already do for stylesheet fetches.
60
+ - `fetchBuffer` in [src/utils.ts](src/utils.ts) now accepts an optional `headers` parameter, parallel to `fetchText`.
61
+
62
+ ### Notes
63
+ - Out of scope: bypassing signed-URL or session-bound foundry protection. That's a v0.4 concern (fail-fast on known commercial CDNs).
64
+
65
+ ## [0.2.1] — 2026-05-27
66
+
67
+ ### Added
68
+ - **Orphan-file auto-download.** In `--headless` mode, font URLs observed in the browser's network log that aren't referenced by any parsed `@font-face` rule (typically from cross-origin stylesheets) are now downloaded automatically into `files/` and listed under a new `orphan_files` array in `fonts.json`.
69
+ - Per-site `README.md` now includes an "Orphan files" section explaining what they are and how to wire them up manually.
70
+
71
+ ### Changed
72
+ - **`fonts.json` shape**: previously a top-level `FontFace[]` array; now an object `{ faces: FontFace[], orphan_files: { file, url }[] }`. Pre-1.0 — no existing consumers — so no migration path was provided.
73
+
74
+ ## [0.2.0] — 2026-05-27
75
+
76
+ ### Added
77
+ - **`--headless` flag** — Playwright/Chromium mode that catches JS-loaded fonts, late-injected `@font-face` rules, and SPA-rendered content. Merges results with the static parser; dedupes faces across both sources.
78
+ - New `src/headless.ts` module with dynamic import of Playwright (graceful fail if not installed).
79
+ - Network-response listener that also observes font URLs at the browser level (logged in v0.2; auto-downloaded in v0.2.1).
80
+ - Playwright is wired as an **optional peer dependency** — the static path stays zero-runtime-deps.
81
+
82
+ ### Changed
83
+ - CLI help text and README document the new `--headless` flag and install steps.
84
+ - Bumped to v0.2.0.
85
+
86
+ ### Notes
87
+ - Headless mode requires `npm install playwright` + `npx playwright install chromium` once per machine.
88
+ - Static mode is unchanged and still the default.
89
+
90
+ ## [0.1.1] — 2026-05-27
91
+
92
+ ### Added
93
+ - **Community font-pairing registry** at `pairings/` — JSON files describing fonts used by real websites, with free OFL alternatives for commercial fonts
94
+ - JSON Schema (`pairings/_schema.json`) with validation rules
95
+ - Issue template (`.github/ISSUE_TEMPLATE/font_pairing.yml`) for non-technical contributors — fill a form, drag a screenshot
96
+ - AI-agent prompt in `pairings/README.md` so anyone can use Claude/ChatGPT/Cursor to draft a pairing JSON
97
+ - CI workflow (`.github/workflows/validate-pairings.yml`) that validates new pairings against the schema on every PR
98
+ - Seed pairings: Stripe, Linear, Vercel
99
+ - Pairings data released under CC0 — public domain, reusable by any third-party tool
100
+
101
+ ### Notes
102
+ - The CLI itself is unchanged in v0.1.1. This release ships repo infrastructure for the community registry.
103
+
104
+ ## [0.1.0] — 2026-05-27
105
+
106
+ ### Added
107
+ - Initial release.
108
+ - CLI: `fontfetch <url> [outDir]`
109
+ - Static `@font-face` parser: linked stylesheets, inline `<style>`, `<link rel="preload" as="font">`
110
+ - Per-site output folder with `files/`, `fonts.css` (local URLs), `fonts.json` manifest, and a human-readable `README.md`
111
+ - Collision-safe filenames across CDNs
112
+ - Node 18+, pure ESM, zero runtime dependencies
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Niyam Vora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,219 @@
1
+ # fontfetch
2
+
3
+ > Download every web font from any site into a project-ready folder — with CSS, manifest, and framework configs ready to drop in.
4
+
5
+ <p>
6
+ <a href="https://www.npmjs.com/package/fontfetch"><img src="https://img.shields.io/npm/v/fontfetch.svg?style=flat-square" alt="npm" /></a>
7
+ <a href="https://github.com/niyamvora/fontfetch/actions"><img src="https://img.shields.io/github/actions/workflow/status/niyamvora/fontfetch/ci.yml?branch=main&style=flat-square" alt="ci" /></a>
8
+ <img src="https://img.shields.io/badge/license-MIT-black?style=flat-square" alt="MIT" />
9
+ <img src="https://img.shields.io/node/v/fontfetch?style=flat-square" alt="node" />
10
+ </p>
11
+
12
+ ```bash
13
+ npx fontfetch https://stripe.com
14
+ ```
15
+
16
+ ```
17
+ → Fetching page: https://stripe.com
18
+ 3 external stylesheet(s), 0 inline <style> block(s)
19
+ → Found 12 @font-face declaration(s), 18 unique file(s)
20
+ ✓ SohneBreit-Buch.woff2 (32,180 bytes)
21
+ ✓ Sohne-Buch.woff2 (28,044 bytes)
22
+ ...
23
+ Done. 18/18 files saved to ./downloaded-fonts/stripe.com
24
+ ```
25
+
26
+ That's it. Real font files, a ready-to-paste `fonts.css` with local URLs, a JSON manifest, and a README — all in one folder you can drag straight into `public/fonts/`.
27
+
28
+ ---
29
+
30
+ ## Why this exists
31
+
32
+ You're mocking up a design. You see a font you like on a marketing site. You want to test it locally for a few hours of iteration — not ship it to production, just see how your design feels with that typography.
33
+
34
+ The existing options aren't great:
35
+ - **`google-webfonts-helper`** — beautiful, but Google Fonts only
36
+ - **`webfont-dl`** — works, but you have to find the CSS URL yourself
37
+ - **Chrome extensions** — point-and-click, no automation, no project integration
38
+
39
+ **fontfetch** takes a URL. Returns a folder. That's the whole product.
40
+
41
+ ## What you get
42
+
43
+ ```
44
+ downloaded-fonts/
45
+ └── stripe.com/
46
+ ├── files/ ← raw woff2 / woff / ttf / otf
47
+ │ ├── Sohne-Buch.woff2
48
+ │ ├── Sohne-Halbfett.woff2
49
+ │ └── ...
50
+ ├── fonts.css ← @font-face block with local URLs
51
+ ├── fonts.json ← manifest: family / weight / style / files
52
+ └── README.md ← human-readable summary, grouped by family
53
+ ```
54
+
55
+ Drop the folder into `public/fonts/` (or wherever), link `fonts.css`, done.
56
+
57
+ ## Install
58
+
59
+ Run on demand:
60
+
61
+ ```bash
62
+ npx fontfetch <url>
63
+ ```
64
+
65
+ Or install globally:
66
+
67
+ ```bash
68
+ npm install -g fontfetch
69
+ fontfetch <url>
70
+ ```
71
+
72
+ Requires Node 18+.
73
+
74
+ ## Usage
75
+
76
+ ```bash
77
+ fontfetch <url> [outDir] [--headless]
78
+ ```
79
+
80
+ | Arg / Flag | Default | Notes |
81
+ |---|---|---|
82
+ | `<url>` | — | Page to download fonts from (use the page where the font is actually rendered) |
83
+ | `[outDir]` | `./downloaded-fonts` | Per-site subfolder is created inside this |
84
+ | `--headless` | off | Launch Playwright/Chromium to also catch JS-loaded fonts |
85
+
86
+ Examples:
87
+
88
+ ```bash
89
+ fontfetch https://stripe.com
90
+ fontfetch https://linear.app ./public/fonts
91
+ fontfetch https://vercel.com /tmp/scratch
92
+ fontfetch https://some-spa.com --headless
93
+ ```
94
+
95
+ ### License review (v0.4)
96
+
97
+ Every pull writes `LICENSE_REVIEW.md` alongside the rest of the per-site output. Each face is classified by a URL-signature heuristic (Adobe Typekit, Monotype, Hoefler, Type Network, etc.) plus a family-name fallback against a curated SIL OFL / Google Fonts catalog snapshot.
98
+
99
+ ```
100
+ → License review: 8 open / 2 commercial / 3 unknown
101
+ ```
102
+
103
+ **Fail-fast.** When every detected font is served from a known commercial-foundry CDN, fontfetch aborts before downloading and emits only `LICENSE_REVIEW.md`. Pass `--force` to download anyway (e.g. for a local mockup of a site whose fonts you've licensed).
104
+
105
+ ```bash
106
+ fontfetch https://commercial-foundry-site.com # aborts, writes LICENSE_REVIEW.md
107
+ fontfetch https://commercial-foundry-site.com --force # downloads anyway
108
+ ```
109
+
110
+ Not legal advice. The classifier is heuristic-only and conservative on purpose — verify before shipping.
111
+
112
+ ### Framework emitters (v0.3)
113
+
114
+ Pass `--emit <target,target,...>` to generate framework-ready config files alongside the default `fonts.css`.
115
+
116
+ ```bash
117
+ fontfetch https://vercel.com --emit next,tailwind
118
+ ```
119
+
120
+ Targets:
121
+
122
+ | Target | Emits | Use it for |
123
+ |---|---|---|
124
+ | `next` | `next.fonts.ts` | Drop-in `next/font/local` config — one `localFont` call per family with all weights, plus a CSS variable |
125
+ | `tailwind` | `tailwind.fonts.ts` | `fontFamily` snippet for `tailwind.config.ts` — `sans` / `serif` / `mono` heuristic + per-family aliases. Pairs with `next` for CSS variables |
126
+ | `vite` | `vite.fonts.md` | Copy-paste integration guide. Vite needs no plugin — the default `fonts.css` is already a drop-in stylesheet |
127
+ | `css` | (default) | Explicit no-op |
128
+
129
+ Output ends up alongside the rest of the per-site bundle:
130
+
131
+ ```
132
+ downloaded-fonts/vercel-com/
133
+ ├── files/
134
+ ├── fonts.css
135
+ ├── fonts.json
136
+ ├── README.md
137
+ ├── next.fonts.ts ← --emit next
138
+ └── tailwind.fonts.ts ← --emit tailwind
139
+ ```
140
+
141
+ ### Headless mode (v0.2)
142
+
143
+ By default fontfetch is **static** — it fetches the HTML, reads every linked stylesheet and inline `<style>`, and parses `@font-face` rules. That covers ~90% of real-world sites and is fast.
144
+
145
+ For SPAs that load fonts at runtime, sites that inject `@font-face` blocks via JavaScript after hydration, or pages behind a Cloudflare challenge, pass `--headless`. fontfetch will launch a headless Chromium via Playwright, wait for `document.fonts.ready`, and dump every `@font-face` rule it can see — merged with the static results.
146
+
147
+ Install Playwright + Chromium once:
148
+
149
+ ```bash
150
+ npm install playwright
151
+ npx playwright install chromium
152
+ ```
153
+
154
+ Then:
155
+
156
+ ```bash
157
+ fontfetch https://example.com --headless
158
+ ```
159
+
160
+ Playwright is an **optional peer dependency** — install it only if you need this mode. The static path runs with zero runtime dependencies.
161
+
162
+ ## How it works
163
+
164
+ 1. Fetches the page HTML
165
+ 2. Pulls every `<link rel="stylesheet">` and inline `<style>` block
166
+ 3. Parses every `@font-face` block: family, weight, style, unicode-range, src
167
+ 4. Also grabs `<link rel="preload" as="font">` references
168
+ 5. Downloads every unique font file
169
+ 6. Rewrites the `@font-face` blocks with local `./files/...` URLs
170
+ 7. Emits `fonts.css`, `fonts.json`, and a `README.md`
171
+
172
+ No browser launched, no dependencies pulled at install time outside of TypeScript build tooling. The whole CLI is one small ESM bundle.
173
+
174
+ ## How it compares
175
+
176
+ | Tool | Any URL | JS-rendered fonts | Framework config emit |
177
+ |---|---|---|---|
178
+ | `google-webfonts-helper` | Google Fonts only | n/a | ✗ |
179
+ | `webfont-dl` | Needs CSS URL | ✗ | ✗ |
180
+ | Chrome extensions | ✓ (manual) | ✓ | ✗ |
181
+ | **`fontfetch`** | ✓ | _v0.2_ | _v0.3_ |
182
+
183
+ ## Roadmap
184
+
185
+ - [x] **v0.1** — Static `@font-face` extraction, ready-to-use CSS, manifest, README
186
+ - [x] **v0.1.1** — [Community font-pairing registry](./docs/roadmap.md#v011--community-font-pairing-registry): share what fonts your favorite sites use, with free OFL alternatives
187
+ - [x] **v0.2** — `--headless` flag: Playwright mode for JS-loaded fonts (Adobe Typekit, SPAs, Cloudflare-protected sites)
188
+ - [x] **v0.2.2** — Referer-aware font downloads (unblocks foundry CDNs that 403 without a Referer)
189
+ - [x] **v0.3** — Framework emitters: `--emit next` / `tailwind` / `vite`
190
+ - [x] **v0.4** — License heuristic + `LICENSE_REVIEW.md` + fail-fast on all-commercial sites (`--force` to bypass)
191
+ - [ ] **v0.5** — [Hosted webapp at `fontfetch.dev`](./docs/roadmap.md#v05--hosted-webapp): URL → live progress → foundry-style previews → compare + pairing
192
+ - [x] **v0.6** — Provenance grouping: output split into `google/` / `adobe-typekit/` / `commercial/` / `open-cdn/` / `self-hosted/`
193
+ - [ ] **v0.4** — License heuristic: flag Google Fonts vs commercial foundries in `LICENSE_REVIEW.md`
194
+ - [ ] **v0.5** — Visual preview gallery: auto-generate `preview.html` with pangrams per family × weight × style
195
+ - [ ] **v0.6** — Provenance grouping: split output into `google/`, `adobe-typekit/`, `self-hosted/`, `cdn/`
196
+
197
+ Want one of these sooner? Open an issue or vote on existing ones.
198
+
199
+ ## Responsible use
200
+
201
+ Font files are software, licensed under EULAs. **fontfetch is intended for local design exploration and testing, not for shipping paid fonts you haven't licensed.** Using a font for a few hours of mockup work in a private project is different from bundling it into a production app. We don't gate the tool — we trust you to know the difference and respect foundry licenses.
202
+
203
+ For production use, the [Google Fonts](https://fonts.google.com) catalog and the [SIL Open Font License](https://openfontlicense.org/) library are designed to be self-hosted freely. Every entry in our [pairings registry](./pairings) lists free alternatives for paid fonts.
204
+
205
+ ## Font pairings registry
206
+
207
+ [`pairings/`](./pairings) is a community-curated list of fonts used by real websites — with **free OFL alternatives** for every commercial font.
208
+
209
+ [**→ Submit a pairing**](https://github.com/niyamvora/fontfetch/issues/new?template=font_pairing.yml) (fill a form, drag a screenshot, done — or [ask an AI to do it for you](./pairings#b-ask-an-ai-to-do-it-for-you)).
210
+
211
+ ## Contributing
212
+
213
+ Issues and PRs welcome. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the dev loop. The codebase is small and approachable — `src/` is a handful of files, no monorepo, no build magic, just `tsup`.
214
+
215
+ Good first issues are tagged `good first issue` on GitHub.
216
+
217
+ ## License
218
+
219
+ [MIT](./LICENSE) — © Niyam Vora