website-to-markdown 1.1.1 → 2.0.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/README.md +135 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/content-type.d.ts.map +1 -1
- package/dist/content-type.js +9 -8
- package/dist/content-type.js.map +1 -1
- package/dist/crawl-snapshot.d.ts +2 -1
- package/dist/crawl-snapshot.d.ts.map +1 -1
- package/dist/crawl-snapshot.js +15 -8
- package/dist/crawl-snapshot.js.map +1 -1
- package/dist/fetch-page.d.ts +38 -22
- package/dist/fetch-page.d.ts.map +1 -1
- package/dist/fetch-page.js +209 -167
- package/dist/fetch-page.js.map +1 -1
- package/dist/hash.d.ts +3 -7
- package/dist/hash.d.ts.map +1 -1
- package/dist/hash.js +3 -7
- package/dist/hash.js.map +1 -1
- package/dist/html-metadata.d.ts.map +1 -1
- package/dist/html-metadata.js +16 -20
- package/dist/html-metadata.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/limit.d.ts +1 -1
- package/dist/limit.js +1 -1
- package/dist/limit.js.map +1 -1
- package/dist/markdown-refs.d.ts +8 -0
- package/dist/markdown-refs.d.ts.map +1 -1
- package/dist/markdown-refs.js +104 -59
- package/dist/markdown-refs.js.map +1 -1
- package/dist/memory-limit.d.ts +1 -1
- package/dist/memory-limit.js +1 -1
- package/dist/memory-limit.js.map +1 -1
- package/dist/net-guard.d.ts +38 -2
- package/dist/net-guard.d.ts.map +1 -1
- package/dist/net-guard.js +88 -17
- package/dist/net-guard.js.map +1 -1
- package/dist/rate-limit-retry.d.ts.map +1 -1
- package/dist/rate-limit-retry.js +12 -21
- package/dist/rate-limit-retry.js.map +1 -1
- package/dist/read-body.d.ts +12 -0
- package/dist/read-body.d.ts.map +1 -0
- package/dist/read-body.js +40 -0
- package/dist/read-body.js.map +1 -0
- package/dist/retry-after.d.ts.map +1 -1
- package/dist/retry-after.js +8 -12
- package/dist/retry-after.js.map +1 -1
- package/dist/schema/index.d.ts +9 -0
- package/dist/schema/index.d.ts.map +1 -1
- package/dist/schema/index.js +28 -0
- package/dist/schema/index.js.map +1 -1
- package/dist/sitemap/index.d.ts +13 -4
- package/dist/sitemap/index.d.ts.map +1 -1
- package/dist/sitemap/index.js +58 -86
- package/dist/sitemap/index.js.map +1 -1
- package/dist/testing/index.d.ts +16 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +54 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/unchanged-page.d.ts +9 -0
- package/dist/unchanged-page.d.ts.map +1 -0
- package/dist/unchanged-page.js +31 -0
- package/dist/unchanged-page.js.map +1 -0
- package/package.json +12 -7
- package/src/cli.ts +3 -1
- package/src/content-type.ts +11 -9
- package/src/crawl-snapshot.ts +21 -8
- package/src/fetch-page.ts +304 -221
- package/src/hash.ts +3 -7
- package/src/html-metadata.ts +20 -18
- package/src/index.ts +7 -0
- package/src/limit.ts +1 -1
- package/src/markdown-refs.ts +129 -57
- package/src/memory-limit.ts +1 -1
- package/src/net-guard.ts +126 -16
- package/src/rate-limit-retry.ts +20 -20
- package/src/read-body.ts +38 -0
- package/src/retry-after.ts +11 -18
- package/src/schema/index.ts +34 -0
- package/src/sitemap/index.ts +83 -86
- package/src/testing/index.ts +60 -0
- package/src/unchanged-page.ts +37 -0
package/README.md
CHANGED
|
@@ -25,11 +25,75 @@ if (page.outcome === 'ok') console.log(page.title, page.markdown, page.links)
|
|
|
25
25
|
```
|
|
26
26
|
|
|
27
27
|
`fetchPageAsMarkdown` never throws: a page that cannot be read comes back as
|
|
28
|
-
an `outcome
|
|
29
|
-
|
|
30
|
-
without converting again. `convertDocumentBytes` converts an uploaded file;
|
|
28
|
+
an `outcome`, and anything unexpected is logged and comes back `transient`.
|
|
29
|
+
`convertDocumentBytes` converts an uploaded file (PDF, office files, images);
|
|
31
30
|
`urlToMarkdown` is the one-shot form that throws.
|
|
32
31
|
|
|
32
|
+
| `outcome` | Meaning | `markdown` |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| `ok` | Read and converted. | the page |
|
|
35
|
+
| `unchanged` | A 304, or the same HTML as the `htmlHash` given. Keep the stored copy. | empty |
|
|
36
|
+
| `gone` | 404 or 410. | empty |
|
|
37
|
+
| `blocked` | A 4xx other than 429, a bot wall Browser Rendering could not pass, or the SSRF guard refused the URL or a redirect. | empty |
|
|
38
|
+
| `transient` | 5xx, a 429 after three paused retries, a network failure, or every conversion tier missed. Try again next run. | empty |
|
|
39
|
+
| `unsupported` | Not a URL we read (bad URL, asset, oversized HTML or document). It will not get better on its own. | empty |
|
|
40
|
+
|
|
41
|
+
`source` says which tier produced the markdown: `content-negotiation` (the
|
|
42
|
+
origin served markdown), `workers-ai`, `browser-rendering`, `not-modified`
|
|
43
|
+
(for `unchanged`) or `firecrawl-parse` (the `parsePdf` tier, whichever parser
|
|
44
|
+
it is). `captureIssue` says why a page is incomplete or missing, when known:
|
|
45
|
+
|
|
46
|
+
| `captureIssue` | |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `markdown_limit` | Markdown cut at `MAX_MARKDOWN_CHARS` (300,000). |
|
|
49
|
+
| `html_byte_limit` | HTML over 2 MB is not converted; over 4 MB it is not downloaded. |
|
|
50
|
+
| `document_byte_limit` | A linked document over 2 MB (PDF: 12 MB). |
|
|
51
|
+
| `workers_ai_rate_limited` | Workers AI kept answering 429. |
|
|
52
|
+
| `browser_rendering_rate_limited` | Browser Rendering kept answering 429. |
|
|
53
|
+
| `render_incomplete` | The page is an app shell and rendering did not fill it. |
|
|
54
|
+
| `pdf_page_limit_unverified` | The PDF parser read 60 pages or could not say how many; the rest may be missing. |
|
|
55
|
+
| `pdf_extraction_failed` | Workers AI and `parsePdf` both returned nothing. |
|
|
56
|
+
|
|
57
|
+
### Reading a page again
|
|
58
|
+
|
|
59
|
+
Pass the last read's `etag` and `lastModified` (sent as conditional headers)
|
|
60
|
+
and `htmlHash` (the hash of the HTML served; most origins send no
|
|
61
|
+
validators). Identical HTML comes back `unchanged` without a Workers AI call.
|
|
62
|
+
The hash includes a conversion version, so after an upgrade that changes the
|
|
63
|
+
conversion every page is converted once more.
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
const next = await converter.fetchPageAsMarkdown({
|
|
67
|
+
url: stored.url,
|
|
68
|
+
etag: stored.etag,
|
|
69
|
+
lastModified: stored.lastModified,
|
|
70
|
+
htmlHash: stored.htmlHash
|
|
71
|
+
})
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`render: false` skips the Browser Rendering tier. `country: 'DK'` adds
|
|
75
|
+
`?country=DK` to the request (not to the page's `url`), which pins a Shopify
|
|
76
|
+
store's market when it would otherwise pick one from the server's IP.
|
|
77
|
+
|
|
78
|
+
Every page's markdown goes through `cleanConvertedMarkdown`: Cloudflare's
|
|
79
|
+
obfuscated e-mail links are decoded, JSON-LD code fences and `<?xml`/`<!DOCTYPE`
|
|
80
|
+
text removed, and a label glued to its own `mailto:`/`tel:` value split. It is
|
|
81
|
+
exported, and idempotent, for markdown stored by an older version.
|
|
82
|
+
|
|
83
|
+
### Limits
|
|
84
|
+
|
|
85
|
+
| | |
|
|
86
|
+
|---|---|
|
|
87
|
+
| HTML converted | 2 MB (read up to 4 MB for links and metadata) |
|
|
88
|
+
| Documents | 2 MB, PDFs 12 MB, PDF pages read by `parsePdf` 60 |
|
|
89
|
+
| Markdown kept | 300,000 characters |
|
|
90
|
+
| Origin request timeout | 30 s, redirects 10 |
|
|
91
|
+
| Pages in flight | `URL_TO_MARKDOWN_PAGE_CONCURRENCY` (80), for the caller's own pool |
|
|
92
|
+
| Per origin host | 16 in flight; 2 (and 2 starts/s) for 15 minutes after a 429 |
|
|
93
|
+
| Workers AI | 24 in flight, 12 starts/s |
|
|
94
|
+
| Browser Rendering | 80 in flight, 28 starts/s |
|
|
95
|
+
| Sitemaps | 20 files, 20,000 URLs, 10 MB per file; robots.txt 500 KiB |
|
|
96
|
+
|
|
33
97
|
Create one converter per process: it owns the Cloudflare pools and the
|
|
34
98
|
per-host limits.
|
|
35
99
|
|
|
@@ -43,16 +107,36 @@ per-host limits.
|
|
|
43
107
|
| `reportTimeouts` | `true` | `false` keeps Workers AI timeouts from `onApiError`; the page falls through to the next tier anyway. |
|
|
44
108
|
| `logger` | silent | `{ info, warn }` |
|
|
45
109
|
| `parsePdf` | none | OCR tier for a PDF Workers AI returned nothing for. |
|
|
46
|
-
| `isHostAllowed` | public addresses only | SSRF guard run before every origin request. |
|
|
110
|
+
| `isHostAllowed` | public addresses only | SSRF guard run before every origin request and every redirect. |
|
|
111
|
+
| `allowLocalhost` | `false` | Let the default guard and `http://` through to `localhost`, `127.0.0.1` and `::1`. Set it outside production for a dev server. |
|
|
112
|
+
| `fetch` | global `fetch` | What origin requests go through, behind the guard. Not used for Cloudflare's API. |
|
|
47
113
|
| `crawlSnapshot` | none | `{ root, logger? }` for the returned `withCrawlSnapshot`. |
|
|
48
114
|
|
|
49
115
|
Besides the converters, the factory returns:
|
|
50
116
|
|
|
117
|
+
- `guardedFetch(url, init?, { maxRedirects? })`: `fetch` behind the
|
|
118
|
+
converter's guard, which follows redirects itself and checks every hop.
|
|
119
|
+
Throws `HostNotAllowedError` when one is refused. Use it for any other
|
|
120
|
+
request to a customer-supplied URL (images, logos, an MCP server).
|
|
51
121
|
- `isHostAllowed(hostname)`: the SSRF guard the converter runs, bound to its
|
|
52
|
-
`logger`.
|
|
122
|
+
`logger`.
|
|
53
123
|
- `withCrawlSnapshot(input, run)`: [crawl snapshots](#crawl-snapshots) under
|
|
54
124
|
`crawlSnapshot.root`. A `null` input just runs `run`.
|
|
55
125
|
|
|
126
|
+
### SSRF guard
|
|
127
|
+
|
|
128
|
+
`isHostAllowed(hostname, { logger?, allowLocalhost? })` resolves the name and
|
|
129
|
+
refuses it when any address is private, loopback, link-local, CGNAT,
|
|
130
|
+
multicast or reserved. An IP literal (`10.0.0.1`, `[fd00::1]`,
|
|
131
|
+
`[::ffff:169.254.169.254]`) is judged as written. A failed DNS lookup is let
|
|
132
|
+
through so the request reports the real error. Results are cached for 60 s;
|
|
133
|
+
DNS rebinding between the check and the connection is not covered.
|
|
134
|
+
|
|
135
|
+
`guardedFetch(url, init?, { isHostAllowed?, fetch?, maxRedirects? })` is the
|
|
136
|
+
standalone form. On a redirect it does what `fetch` does: `Authorization` and
|
|
137
|
+
`Cookie` are dropped when the origin changes, and a 303 (or a POST answered
|
|
138
|
+
with 301/302) becomes a GET.
|
|
139
|
+
|
|
56
140
|
A limiter is a function `run => Promise` with `tripCooldown(ms)` and
|
|
57
141
|
`dispose()`:
|
|
58
142
|
|
|
@@ -103,6 +187,10 @@ const CrawlSnapshotInput = z.object({
|
|
|
103
187
|
})
|
|
104
188
|
```
|
|
105
189
|
|
|
190
|
+
`website-to-markdown/schema` also has `FETCH_OUTCOMES`, `FETCH_SOURCES` and
|
|
191
|
+
`CAPTURE_ISSUES`, the runtime lists behind the types, for narrowing a value
|
|
192
|
+
read back from storage: `z.enum(FETCH_OUTCOMES)`.
|
|
193
|
+
|
|
106
194
|
## Sitemaps
|
|
107
195
|
|
|
108
196
|
```ts
|
|
@@ -113,6 +201,29 @@ const pages = await discoverSitemapUrls('https://example.com', robots.sitemaps)
|
|
|
113
201
|
const readable = pages.filter((page) => robotsAllows(robots.rules, page.url))
|
|
114
202
|
```
|
|
115
203
|
|
|
204
|
+
Both read through `guardedFetch` with the default guard and send the browser
|
|
205
|
+
user agent. Pass `{ fetch: converter.guardedFetch }` as the last argument to
|
|
206
|
+
use the converter's guard, or any `fetch` in tests. Neither throws: a site
|
|
207
|
+
without a robots.txt or a sitemap gives `[]`.
|
|
208
|
+
|
|
209
|
+
## Testing
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import { fakePage, fakeSite } from 'website-to-markdown/testing'
|
|
213
|
+
|
|
214
|
+
const page = fakePage({ url: 'https://a.dk', markdown: '# A' }) // a complete ok page
|
|
215
|
+
|
|
216
|
+
const site = fakeSite({
|
|
217
|
+
'https://a.dk': { markdown: '# A', links: ['https://a.dk/kontakt'], etag: 'v1' }
|
|
218
|
+
})
|
|
219
|
+
await site.fetchPageAsMarkdown({ url: 'https://a.dk', etag: 'v1' }) // unchanged, 304
|
|
220
|
+
await site.fetchPageAsMarkdown({ url: 'https://a.dk/x' }) // gone, 404
|
|
221
|
+
site.calls // every input, in order
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
`createMemoryConcurrencyLimit` as `createLimit` keeps a test off a shared
|
|
225
|
+
limiter, and the `fetch` option keeps it off the network.
|
|
226
|
+
|
|
116
227
|
## CLI
|
|
117
228
|
|
|
118
229
|
```sh
|
|
@@ -129,3 +240,22 @@ bunx website-to-markdown https://example.com --env-file .env.cloudflare
|
|
|
129
240
|
|
|
130
241
|
infisical run -- bunx website-to-markdown https://example.com
|
|
131
242
|
```
|
|
243
|
+
|
|
244
|
+
## Upgrading from 1.x
|
|
245
|
+
|
|
246
|
+
- Localhost is refused unless you pass `allowLocalhost: true` (1.x allowed it
|
|
247
|
+
whenever `NODE_ENV` was not `production`). For a dev server:
|
|
248
|
+
`createWebsiteToMarkdown({ …, allowLocalhost: process.env.NODE_ENV !== 'production' })`.
|
|
249
|
+
The standalone `isHostAllowed(hostname, logger)` is now
|
|
250
|
+
`isHostAllowed(hostname, { logger, allowLocalhost })`.
|
|
251
|
+
- `FetchedPage` has no optional fields: `alternates`, `alternateLanguages`,
|
|
252
|
+
`captureIssue` and `htmlHash` are always set. Build test pages with
|
|
253
|
+
`fakePage` from `website-to-markdown/testing`.
|
|
254
|
+
- `captureIssue` is the `CaptureIssue` union instead of `string`. A Browser
|
|
255
|
+
Rendering 429 is `browser_rendering_rate_limited`, no longer
|
|
256
|
+
`workers_ai_rate_limited`.
|
|
257
|
+
- Redirects are followed by the library, so every hop passes the guard; a
|
|
258
|
+
redirect to a refused host is `blocked`. Sitemaps and robots.txt go through
|
|
259
|
+
the guard too.
|
|
260
|
+
- The conversion version is bumped, so the first read of each page after the
|
|
261
|
+
upgrade converts it again even when its `htmlHash` matches.
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAOA,OAAO,EACN,KAAK,KAAK,EAKV,MAAM,YAAY,CAAA;AAInB,eAAO,MAAM,GAAG,
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAOA,OAAO,EACN,KAAK,KAAK,EAKV,MAAM,YAAY,CAAA;AAInB,eAAO,MAAM,GAAG,0BAuFd,CAAA;AAQF,0DAA0D;AAC1D,wBAAsB,IAAI,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,EAAE,CAAC,EAAE,KAAK,mBAG7D"}
|
package/dist/cli.js
CHANGED
|
@@ -46,7 +46,9 @@ export const cli = defineCli({
|
|
|
46
46
|
cloudflare: {
|
|
47
47
|
apiToken: CLOUDFLARE_API_TOKEN,
|
|
48
48
|
accountId: CLOUDFLARE_ACCOUNT_ID
|
|
49
|
-
}
|
|
49
|
+
},
|
|
50
|
+
// Run by a person on their own machine, so a dev server is fair game.
|
|
51
|
+
allowLocalhost: true
|
|
50
52
|
});
|
|
51
53
|
ctx.stderr.write(`Fetching ${url}…\n`);
|
|
52
54
|
const page = await fetchPageAsMarkdown({ url, render });
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;GAGG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAA;AACpC,OAAO,EAEN,QAAQ,EACR,aAAa,EACb,SAAS,EACT,aAAa,EACb,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AACvB,OAAO,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAA;AAEzD,MAAM,CAAC,MAAM,GAAG,GAAG,SAAS,CAAC;IAC5B,IAAI,EAAE,qBAAqB;IAC3B,kDAAkD;IAClD,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE;QACtB,IACC,KAAK,YAAY,aAAa;YAC9B,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,EACnE,CAAC;YACF,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAC3B,OAAO,CAAC,CAAA;QACT,CAAC;IACF,CAAC;IACD,QAAQ,EAAE,aAAa,CAAC;QACvB,8DAA8D;QAC9D,MAAM,EAAE,KAAK;QACb,WAAW,EACV,uFAAuF;QACxF,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC;YACf,GAAG,EAAE,CAAC;iBACJ,MAAM,EAAE;iBACR,QAAQ,CAAC,gCAAgC,CAAC;iBAC1C,IAAI,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;YAC5B,MAAM,EAAE,CAAC;iBACP,OAAO,EAAE;iBACT,OAAO,CAAC,IAAI,CAAC;iBACb,QAAQ,CACR,kEAAkE,CAClE;YACF,OAAO,EAAE,CAAC;iBACR,MAAM,EAAE;iBACR,QAAQ,EAAE;iBACV,QAAQ,CAAC,6DAA6D,CAAC;SACzE,CAAC;QACF,GAAG,EAAE,KAAK,EAAE,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,EAAE;YAC5C,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;YAChE,MAAM,oBAAoB,GACzB,GAAG,CAAC,GAAG,CAAC,oBAAoB,IAAI,IAAI,CAAC,oBAAoB,CAAA;YAC1D,MAAM,qBAAqB,GAC1B,GAAG,CAAC,GAAG,CAAC,qBAAqB,IAAI,IAAI,CAAC,qBAAqB,CAAA;YAC5D,IAAI,CAAC,oBAAoB,IAAI,CAAC,qBAAqB;gBAClD,MAAM,IAAI,QAAQ,CACjB,qDAAqD,CACrD,CAAA;YAEF,MAAM,EAAE,mBAAmB,EAAE,GAAG,uBAAuB,CAAC;gBACvD,UAAU,EAAE;oBACX,QAAQ,EAAE,oBAAoB;oBAC9B,SAAS,EAAE,qBAAqB;iBAChC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env bun\n/**\n * Fetch a public URL as markdown and print the result. Page metadata goes to\n * stderr; markdown goes to stdout.\n */\nimport { readFile } from 'node:fs/promises'\nimport { parseEnv } from 'node:util'\nimport {\n\ttype CliIO,\n\tCliError,\n\tCliUsageError,\n\tdefineCli,\n\tdefineCommand\n} from 'hanzio/cli'\nimport { z } from 'zod'\nimport { createWebsiteToMarkdown } from './fetch-page.js'\n\nexport const cli = defineCli({\n\tname: 'website-to-markdown',\n\t// No URL prints the help and exits 1, as 1.0 did.\n\tonError: (error, io) => {\n\t\tif (\n\t\t\terror instanceof CliUsageError &&\n\t\t\terror.message.split('\\n').some((line) => line.startsWith('<url>: '))\n\t\t) {\n\t\t\tio.stdout.write(error.help)\n\t\t\treturn 1\n\t\t}\n\t},\n\tcommands: defineCommand({\n\t\t// Unknown flags and extra positionals are ignored, as in 1.0.\n\t\tstrict: false,\n\t\tdescription:\n\t\t\t'Fetch a public URL as markdown. Needs CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.',\n\t\tinput: z.object({\n\t\t\turl: z\n\t\t\t\t.string()\n\t\t\t\t.describe('e.g. https://example.com/about')\n\t\t\t\t.meta({ positional: true }),\n\t\t\trender: z\n\t\t\t\t.boolean()\n\t\t\t\t.default(true)\n\t\t\t\t.describe(\n\t\t\t\t\t'Use the Browser Rendering tier; --no-render keeps to HTML tiers.'\n\t\t\t\t),\n\t\t\tenvFile: z\n\t\t\t\t.string()\n\t\t\t\t.optional()\n\t\t\t\t.describe('Read CLOUDFLARE_* from a dotenv file; the environment wins.')\n\t\t}),\n\t\trun: async ({ url, render, envFile }, ctx) => {\n\t\t\tconst file = envFile ? parseEnv(await readEnvFile(envFile)) : {}\n\t\t\tconst CLOUDFLARE_API_TOKEN =\n\t\t\t\tctx.env.CLOUDFLARE_API_TOKEN || file.CLOUDFLARE_API_TOKEN\n\t\t\tconst CLOUDFLARE_ACCOUNT_ID =\n\t\t\t\tctx.env.CLOUDFLARE_ACCOUNT_ID || file.CLOUDFLARE_ACCOUNT_ID\n\t\t\tif (!CLOUDFLARE_API_TOKEN || !CLOUDFLARE_ACCOUNT_ID)\n\t\t\t\tthrow new CliError(\n\t\t\t\t\t'Set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID.'\n\t\t\t\t)\n\n\t\t\tconst { fetchPageAsMarkdown } = createWebsiteToMarkdown({\n\t\t\t\tcloudflare: {\n\t\t\t\t\tapiToken: CLOUDFLARE_API_TOKEN,\n\t\t\t\t\taccountId: CLOUDFLARE_ACCOUNT_ID\n\t\t\t\t},\n\t\t\t\t// Run by a person on their own machine, so a dev server is fair game.\n\t\t\t\tallowLocalhost: true\n\t\t\t})\n\n\t\t\tctx.stderr.write(`Fetching ${url}…\\n`)\n\n\t\t\tconst page = await fetchPageAsMarkdown({ url, render })\n\n\t\t\tctx.stderr.write(\n\t\t\t\t`${JSON.stringify(\n\t\t\t\t\t{\n\t\t\t\t\t\toutcome: page.outcome,\n\t\t\t\t\t\tstatus: page.status,\n\t\t\t\t\t\tsource: page.source,\n\t\t\t\t\t\turl: page.url,\n\t\t\t\t\t\tfinalUrl: page.finalUrl,\n\t\t\t\t\t\tcontentType: page.contentType,\n\t\t\t\t\t\tetag: page.etag,\n\t\t\t\t\t\tlastModified: page.lastModified,\n\t\t\t\t\t\ttitle: page.title,\n\t\t\t\t\t\tlang: page.lang,\n\t\t\t\t\t\tcanonical: page.canonical,\n\t\t\t\t\t\tmetaDescription: page.metaDescription,\n\t\t\t\t\t\tjsonLdBlocks: page.jsonLd.length,\n\t\t\t\t\t\tlinks: page.links.length,\n\t\t\t\t\t\timages: page.images.length,\n\t\t\t\t\t\tmarkdownChars: page.markdown.length,\n\t\t\t\t\t\tdurationMs: page.durationMs\n\t\t\t\t\t},\n\t\t\t\t\tnull,\n\t\t\t\t\t2\n\t\t\t\t)}\\n`\n\t\t\t)\n\n\t\t\tif (page.outcome !== 'ok') ctx.exitCode = 1\n\t\t\treturn page.markdown\n\t\t}\n\t})\n})\n\nfunction readEnvFile(path: string) {\n\treturn readFile(path, 'utf8').catch(() => {\n\t\tthrow new CliError(`Cannot read --env-file ${path}.`)\n\t})\n}\n\n/** The bin. `--help` without a URL exits 1, as in 1.0. */\nexport async function main(argv: readonly string[], io?: CliIO) {\n\tconst code = await cli.run(argv, io)\n\treturn code === 0 && argv.every((arg) => arg.startsWith('-')) ? 1 : code\n}\n\nif (import.meta.main) process.exitCode = await main(process.argv.slice(2))\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"content-type.d.ts","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AA6DA,kFAAkF;AAClF,wBAAgB,uBAAuB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;;;SAIjE;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAA;AAE7E,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAO3E"}
|
package/dist/content-type.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
import { isOneOf } from 'hanzio';
|
|
2
|
+
const MARKDOWN_CONTENT_TYPES = [
|
|
2
3
|
'text/markdown',
|
|
3
4
|
'text/x-markdown',
|
|
4
5
|
'application/markdown'
|
|
5
|
-
]
|
|
6
|
-
const HTML_CONTENT_TYPES =
|
|
7
|
-
const TEXTUAL_CONTENT_TYPES =
|
|
6
|
+
];
|
|
7
|
+
const HTML_CONTENT_TYPES = ['text/html', 'application/xhtml+xml'];
|
|
8
|
+
const TEXTUAL_CONTENT_TYPES = [
|
|
8
9
|
'application/xml',
|
|
9
10
|
'application/json',
|
|
10
11
|
'application/rss+xml',
|
|
11
12
|
'application/atom+xml'
|
|
12
|
-
]
|
|
13
|
+
];
|
|
13
14
|
/**
|
|
14
15
|
* The formats Workers AI `toMarkdown` accepts as documents (its `/supported`
|
|
15
16
|
* list, read 2026-09-08). Images are accepted there too but are deliberately
|
|
@@ -44,7 +45,7 @@ function normalizeContentType(contentType) {
|
|
|
44
45
|
function isTextualContentType(type) {
|
|
45
46
|
return (type === '' ||
|
|
46
47
|
type.startsWith('text/') ||
|
|
47
|
-
TEXTUAL_CONTENT_TYPES
|
|
48
|
+
isOneOf(TEXTUAL_CONTENT_TYPES, type) ||
|
|
48
49
|
// `application/ld+json`, `application/rdf+xml`, … but not `image/svg+xml`.
|
|
49
50
|
(type.startsWith('application/') &&
|
|
50
51
|
(type.endsWith('+json') || type.endsWith('+xml'))));
|
|
@@ -58,9 +59,9 @@ export function convertibleDocumentType(contentType) {
|
|
|
58
59
|
/** What the fetch layer should do with a body, decided from the header alone. */
|
|
59
60
|
export function classifyContentType(contentType) {
|
|
60
61
|
const type = normalizeContentType(contentType);
|
|
61
|
-
if (MARKDOWN_CONTENT_TYPES
|
|
62
|
+
if (isOneOf(MARKDOWN_CONTENT_TYPES, type))
|
|
62
63
|
return 'markdown';
|
|
63
|
-
if (HTML_CONTENT_TYPES
|
|
64
|
+
if (isOneOf(HTML_CONTENT_TYPES, type))
|
|
64
65
|
return 'html';
|
|
65
66
|
if (CONVERTIBLE_DOCUMENT_EXTENSIONS.has(type))
|
|
66
67
|
return 'document';
|
package/dist/content-type.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"content-type.js","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AAAA,MAAM,
|
|
1
|
+
{"version":3,"file":"content-type.js","sourceRoot":"","sources":["../src/content-type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAiB,MAAM,QAAQ,CAAA;AAE/C,MAAM,sBAAsB,GAAG;IAC9B,eAAe;IACf,iBAAiB;IACjB,sBAAsB;CACb,CAAA;AAEV,MAAM,kBAAkB,GAAG,CAAC,WAAW,EAAE,uBAAuB,CAAU,CAAA;AAE1E,MAAM,qBAAqB,GAAG;IAC7B,iBAAiB;IACjB,kBAAkB;IAClB,qBAAqB;IACrB,sBAAsB;CACb,CAAA;AAEV;;;;;GAKG;AACH,MAAM,+BAA+B,GAAG,IAAI,GAAG,CAAC;IAC/C,CAAC,iBAAiB,EAAE,KAAK,CAAC;IAC1B;QACC,yEAAyE;QACzE,MAAM;KACN;IACD,CAAC,yCAAyC,EAAE,KAAK,CAAC;IAClD,CAAC,mEAAmE,EAAE,MAAM,CAAC;IAC7E,CAAC,gDAAgD,EAAE,MAAM,CAAC;IAC1D,CAAC,uDAAuD,EAAE,MAAM,CAAC;IACjE,CAAC,0BAA0B,EAAE,KAAK,CAAC;IACnC,CAAC,gDAAgD,EAAE,KAAK,CAAC;IACzD;QACC,2EAA2E;QAC3E,MAAM;KACN;IACD;QACC,wEAAwE;QACxE,MAAM;KACN;IACD,CAAC,iDAAiD,EAAE,KAAK,CAAC;CAC1D,CAAC,CAAA;AAEF,SAAS,oBAAoB,CAAC,WAA6B;IAC1D,OAAO,WAAW,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,IAAI,EAAE,CAAA;AAC9D,CAAC;AAED,SAAS,oBAAoB,CAAC,IAAY;IACzC,OAAO,CACN,IAAI,KAAK,EAAE;QACX,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QACxB,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC;QACpC,2EAA2E;QAC3E,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC;YAC/B,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CACnD,CAAA;AACF,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,uBAAuB,CAAC,WAA0B;IACjE,MAAM,QAAQ,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAA;IAClD,MAAM,SAAS,GAAG,+BAA+B,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC/D,OAAO,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AAClD,CAAC;AAID,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,WAA0B;IAC7D,MAAM,IAAI,GAAG,oBAAoB,CAAC,WAAW,CAAC,CAAA;IAC9C,IAAI,OAAO,CAAC,sBAAsB,EAAE,IAAI,CAAC;QAAE,OAAO,UAAU,CAAA;IAC5D,IAAI,OAAO,CAAC,kBAAkB,EAAE,IAAI,CAAC;QAAE,OAAO,MAAM,CAAA;IACpD,IAAI,+BAA+B,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,UAAU,CAAA;IAChE,IAAI,oBAAoB,CAAC,IAAI,CAAC;QAAE,OAAO,MAAM,CAAA;IAC7C,OAAO,OAAO,CAAA;AACf,CAAC","sourcesContent":["import { isOneOf, type Nullable } from 'hanzio'\n\nconst MARKDOWN_CONTENT_TYPES = [\n\t'text/markdown',\n\t'text/x-markdown',\n\t'application/markdown'\n] as const\n\nconst HTML_CONTENT_TYPES = ['text/html', 'application/xhtml+xml'] as const\n\nconst TEXTUAL_CONTENT_TYPES = [\n\t'application/xml',\n\t'application/json',\n\t'application/rss+xml',\n\t'application/atom+xml'\n] as const\n\n/**\n * The formats Workers AI `toMarkdown` accepts as documents (its `/supported`\n * list, read 2026-09-08). Images are accepted there too but are deliberately\n * absent: this list decides what the CRAWLER downloads and converts, and a\n * site's gallery must stay an asset. Uploads accept images on their own list.\n */\nconst CONVERTIBLE_DOCUMENT_EXTENSIONS = new Map([\n\t['application/pdf', 'pdf'],\n\t[\n\t\t'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n\t\t'docx'\n\t],\n\t['application/vnd.oasis.opendocument.text', 'odt'],\n\t['application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'xlsx'],\n\t['application/vnd.ms-excel.sheet.macroenabled.12', 'xlsm'],\n\t['application/vnd.ms-excel.sheet.binary.macroenabled.12', 'xlsb'],\n\t['application/vnd.ms-excel', 'xls'],\n\t['application/vnd.oasis.opendocument.spreadsheet', 'ods'],\n\t[\n\t\t'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n\t\t'pptx'\n\t],\n\t[\n\t\t'application/vnd.openxmlformats-officedocument.presentationml.slideshow',\n\t\t'ppsx'\n\t],\n\t['application/vnd.oasis.opendocument.presentation', 'odp']\n])\n\nfunction normalizeContentType(contentType: Nullable<string>) {\n\treturn contentType?.split(';')[0]?.trim().toLowerCase() ?? ''\n}\n\nfunction isTextualContentType(type: string) {\n\treturn (\n\t\ttype === '' ||\n\t\ttype.startsWith('text/') ||\n\t\tisOneOf(TEXTUAL_CONTENT_TYPES, type) ||\n\t\t// `application/ld+json`, `application/rdf+xml`, … but not `image/svg+xml`.\n\t\t(type.startsWith('application/') &&\n\t\t\t(type.endsWith('+json') || type.endsWith('+xml')))\n\t)\n}\n\n/** The upload descriptor for a binary we can convert, or null for text/assets. */\nexport function convertibleDocumentType(contentType: string | null) {\n\tconst mimeType = normalizeContentType(contentType)\n\tconst extension = CONVERTIBLE_DOCUMENT_EXTENSIONS.get(mimeType)\n\treturn extension ? { extension, mimeType } : null\n}\n\nexport type ContentKind = 'markdown' | 'html' | 'text' | 'document' | 'asset'\n\n/** What the fetch layer should do with a body, decided from the header alone. */\nexport function classifyContentType(contentType: string | null): ContentKind {\n\tconst type = normalizeContentType(contentType)\n\tif (isOneOf(MARKDOWN_CONTENT_TYPES, type)) return 'markdown'\n\tif (isOneOf(HTML_CONTENT_TYPES, type)) return 'html'\n\tif (CONVERTIBLE_DOCUMENT_EXTENSIONS.has(type)) return 'document'\n\tif (isTextualContentType(type)) return 'text'\n\treturn 'asset'\n}\n"]}
|
package/dist/crawl-snapshot.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type Nullable } from 'hanzio';
|
|
1
2
|
import type { Logger } from './logger.js';
|
|
2
3
|
import { CRAWL_SNAPSHOT_NAME_PATTERN, type CrawlSnapshotInput } from './schema/index.js';
|
|
3
4
|
export { CRAWL_SNAPSHOT_NAME_PATTERN, type CrawlSnapshotInput };
|
|
@@ -7,7 +8,7 @@ export type CrawlSnapshotOptions = {
|
|
|
7
8
|
/** Told about replay misses. */
|
|
8
9
|
logger?: Logger | undefined;
|
|
9
10
|
};
|
|
10
|
-
export declare function withCrawlSnapshot<T>(input: CrawlSnapshotInput
|
|
11
|
+
export declare function withCrawlSnapshot<T>(input: Nullable<CrawlSnapshotInput>, run: () => Promise<T>, options: CrawlSnapshotOptions): Promise<T>;
|
|
11
12
|
/**
|
|
12
13
|
* `read` through the active snapshot. Recording stores what the network
|
|
13
14
|
* returned; replaying returns what was stored. A read the recording never made
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crawl-snapshot.d.ts","sourceRoot":"","sources":["../src/crawl-snapshot.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"crawl-snapshot.d.ts","sourceRoot":"","sources":["../src/crawl-snapshot.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAEjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EACN,2BAA2B,EAC3B,KAAK,kBAAkB,EACvB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,2BAA2B,EAAE,KAAK,kBAAkB,EAAE,CAAA;AAsB/D,MAAM,MAAM,oBAAoB,GAAG;IAClC,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAA;IACZ,gCAAgC;IAChC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CAC3B,CAAA;AAID,wBAAgB,iBAAiB,CAAC,CAAC,EAClC,KAAK,EAAE,QAAQ,CAAC,kBAAkB,CAAC,EACnC,GAAG,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACrB,OAAO,EAAE,oBAAoB,cAqB7B;AAED;;;;;GAKG;AACH,wBAAsB,oBAAoB,CAAC,CAAC,EAC3C,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACpB,OAAO,CAAC,CAAC,CAAC,CAiBZ;AAUD,8EAA8E;AAC9E,wBAAgB,oBAAoB,IAAI,kBAAkB,GAAG,IAAI,CAGhE"}
|
package/dist/crawl-snapshot.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
+
import { invariant } from 'hanzio';
|
|
4
5
|
import { sha256Hex } from './hash.js';
|
|
5
6
|
import { CRAWL_SNAPSHOT_NAME_PATTERN } from './schema/index.js';
|
|
6
7
|
export { CRAWL_SNAPSHOT_NAME_PATTERN };
|
|
@@ -8,12 +9,8 @@ const snapshot = new AsyncLocalStorage();
|
|
|
8
9
|
export function withCrawlSnapshot(input, run, options) {
|
|
9
10
|
if (!input)
|
|
10
11
|
return run();
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
if (!CRAWL_SNAPSHOT_NAME_PATTERN.test(input.name)) {
|
|
15
|
-
throw new Error(`Invalid crawl snapshot name: ${input.name}`);
|
|
16
|
-
}
|
|
12
|
+
invariant(process.env.NODE_ENV !== 'production', 'A crawl snapshot is a local bench tool');
|
|
13
|
+
invariant(CRAWL_SNAPSHOT_NAME_PATTERN.test(input.name), `Invalid crawl snapshot name: ${input.name}`);
|
|
17
14
|
return snapshot.run({
|
|
18
15
|
mode: input.mode,
|
|
19
16
|
name: input.name,
|
|
@@ -34,8 +31,10 @@ export async function throughCrawlSnapshot(key, read) {
|
|
|
34
31
|
const file = resolve(active.directory, `${sha256Hex(key).slice(0, 32)}.json`);
|
|
35
32
|
if (active.mode === 'replay') {
|
|
36
33
|
const stored = await readFile(file, 'utf8').catch(() => null);
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
// A file cut short by a killed recording is a miss, not a crash.
|
|
35
|
+
const parsed = stored === null ? null : parseStored(stored);
|
|
36
|
+
if (parsed)
|
|
37
|
+
return parsed.value;
|
|
39
38
|
active.logger?.warn('crawl-snapshot.miss', { key });
|
|
40
39
|
}
|
|
41
40
|
const value = await read();
|
|
@@ -43,6 +42,14 @@ export async function throughCrawlSnapshot(key, read) {
|
|
|
43
42
|
await writeFile(file, JSON.stringify({ key, value }));
|
|
44
43
|
return value;
|
|
45
44
|
}
|
|
45
|
+
function parseStored(stored) {
|
|
46
|
+
try {
|
|
47
|
+
return JSON.parse(stored);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
46
53
|
/** The snapshot this job reads or writes, to hand on to the job it queues. */
|
|
47
54
|
export function currentCrawlSnapshot() {
|
|
48
55
|
const active = snapshot.getStore();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"crawl-snapshot.js","sourceRoot":"","sources":["../src/crawl-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC7D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,OAAO,EACN,2BAA2B,EAE3B,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,2BAA2B,EAA2B,CAAA;AA6B/D,MAAM,QAAQ,GAAG,IAAI,iBAAiB,EAAiB,CAAA;AAEvD,MAAM,UAAU,iBAAiB,CAChC,
|
|
1
|
+
{"version":3,"file":"crawl-snapshot.js","sourceRoot":"","sources":["../src/crawl-snapshot.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { resolve } from 'node:path'\nimport { invariant, type Nullable } from 'hanzio'\nimport { sha256Hex } from './hash.js'\nimport type { Logger } from './logger.js'\nimport {\n\tCRAWL_SNAPSHOT_NAME_PATTERN,\n\ttype CrawlSnapshotInput\n} from './schema/index.js'\n\nexport { CRAWL_SNAPSHOT_NAME_PATTERN, type CrawlSnapshotInput }\n\n/**\n * A crawl recorded once and replayed, for benchmarks. Two runs of a pipeline\n * then read the same pages, so a difference between them is the code: not a\n * site that published a page in between, a 429, or a host that timed out on\n * one run and not the other.\n *\n * Recorded: the network reads - pages as `fetchPageAsMarkdown` returns them,\n * robots.txt and sitemaps - plus anything the caller routes through\n * `throughCrawlSnapshot`. Everything else runs live on every replay, which is\n * the point.\n *\n * Set per job through `withCrawlSnapshot`, never globally: a worker drains\n * other tenants' jobs on the same lane. Never in production.\n */\n\ntype CrawlSnapshot = CrawlSnapshotInput & {\n\tdirectory: string\n\tlogger: Logger | undefined\n}\n\nexport type CrawlSnapshotOptions = {\n\t/** Directory the snapshots live under. */\n\troot: string\n\t/** Told about replay misses. */\n\tlogger?: Logger | undefined\n}\n\nconst snapshot = new AsyncLocalStorage<CrawlSnapshot>()\n\nexport function withCrawlSnapshot<T>(\n\tinput: Nullable<CrawlSnapshotInput>,\n\trun: () => Promise<T>,\n\toptions: CrawlSnapshotOptions\n) {\n\tif (!input) return run()\n\tinvariant(\n\t\tprocess.env.NODE_ENV !== 'production',\n\t\t'A crawl snapshot is a local bench tool'\n\t)\n\tinvariant(\n\t\tCRAWL_SNAPSHOT_NAME_PATTERN.test(input.name),\n\t\t`Invalid crawl snapshot name: ${input.name}`\n\t)\n\n\treturn snapshot.run(\n\t\t{\n\t\t\tmode: input.mode,\n\t\t\tname: input.name,\n\t\t\tdirectory: resolve(options.root, input.name),\n\t\t\tlogger: options.logger\n\t\t},\n\t\trun\n\t)\n}\n\n/**\n * `read` through the active snapshot. Recording stores what the network\n * returned; replaying returns what was stored. A read the recording never made\n * (a code change that discovers a new URL) goes to the network and is added,\n * with a warning, so the snapshot stays complete without hiding the drift.\n */\nexport async function throughCrawlSnapshot<T>(\n\tkey: string,\n\tread: () => Promise<T>\n): Promise<T> {\n\tconst active = snapshot.getStore()\n\tif (!active) return read()\n\n\tconst file = resolve(active.directory, `${sha256Hex(key).slice(0, 32)}.json`)\n\tif (active.mode === 'replay') {\n\t\tconst stored = await readFile(file, 'utf8').catch(() => null)\n\t\t// A file cut short by a killed recording is a miss, not a crash.\n\t\tconst parsed = stored === null ? null : parseStored<T>(stored)\n\t\tif (parsed) return parsed.value\n\t\tactive.logger?.warn('crawl-snapshot.miss', { key })\n\t}\n\n\tconst value = await read()\n\tawait mkdir(active.directory, { recursive: true })\n\tawait writeFile(file, JSON.stringify({ key, value }))\n\treturn value\n}\n\nfunction parseStored<T>(stored: string) {\n\ttry {\n\t\treturn JSON.parse(stored) as { value: T }\n\t} catch {\n\t\treturn null\n\t}\n}\n\n/** The snapshot this job reads or writes, to hand on to the job it queues. */\nexport function currentCrawlSnapshot(): CrawlSnapshotInput | null {\n\tconst active = snapshot.getStore()\n\treturn active ? { mode: active.mode, name: active.name } : null\n}\n"]}
|
package/dist/fetch-page.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { type Nullable } from 'hanzio';
|
|
1
2
|
import type { ApiError } from 'hanzio/api-wrapper';
|
|
2
3
|
import { type CloudflareCredentials } from './client.js';
|
|
3
4
|
import { type CrawlSnapshotInput, type CrawlSnapshotOptions } from './crawl-snapshot.js';
|
|
4
5
|
import { type CreateConcurrencyLimit } from './limit.js';
|
|
5
6
|
import { type Logger } from './logger.js';
|
|
7
|
+
import { type Fetch, type GuardedFetchOptions } from './net-guard.js';
|
|
8
|
+
import type { CaptureIssue, FetchOutcome, FetchSource } from './schema/index.js';
|
|
9
|
+
export type { CaptureIssue, FetchOutcome, FetchSource };
|
|
6
10
|
/** Stored markdown ceiling; a page over this is a listing or an upload, not knowledge. */
|
|
7
11
|
export declare const MAX_MARKDOWN_CHARS = 300000;
|
|
8
|
-
export type FetchOutcome = 'ok' | 'unchanged' | 'gone' | 'blocked' | 'transient' | 'unsupported';
|
|
9
|
-
export type FetchSource = 'content-negotiation' | 'workers-ai' | 'browser-rendering' | 'not-modified' | 'firecrawl-parse';
|
|
10
12
|
export type FetchedPage = {
|
|
11
13
|
/** The URL we requested, after http->https upgrade and www fallback. */
|
|
12
14
|
url: string;
|
|
@@ -27,13 +29,13 @@ export type FetchedPage = {
|
|
|
27
29
|
lang: string | null;
|
|
28
30
|
/** <link rel="canonical">, absolute. */
|
|
29
31
|
canonical: string | null;
|
|
30
|
-
/** `<link rel="alternate" hreflang>` targets, absolute, `x-default` excluded.
|
|
31
|
-
alternates
|
|
32
|
+
/** `<link rel="alternate" hreflang>` targets, absolute, `x-default` excluded. */
|
|
33
|
+
alternates: string[];
|
|
32
34
|
/** `alternates` by language: URL -> ISO 639-1 (`da` of `da-DK`). */
|
|
33
|
-
alternateLanguages
|
|
35
|
+
alternateLanguages: Record<string, string>;
|
|
34
36
|
/** <meta name="description"> or the og: equivalent. */
|
|
35
37
|
metaDescription: string | null;
|
|
36
|
-
/** Parsed application/ld+json blocks
|
|
38
|
+
/** Parsed application/ld+json blocks, any shape. */
|
|
37
39
|
jsonLd: unknown[];
|
|
38
40
|
/**
|
|
39
41
|
* Absolute, deduped link targets: the markdown's, unioned with every
|
|
@@ -44,38 +46,39 @@ export type FetchedPage = {
|
|
|
44
46
|
/** Absolute, deduped image URLs found in the markdown. */
|
|
45
47
|
images: string[];
|
|
46
48
|
durationMs: number;
|
|
47
|
-
|
|
49
|
+
/** Why the page is incomplete or missing, when the library knows. */
|
|
50
|
+
captureIssue: CaptureIssue | null;
|
|
48
51
|
/**
|
|
49
52
|
* Hash of the HTML the origin served, when an origin tier read HTML. Given
|
|
50
53
|
* back on the next read (`fetchPageAsMarkdown({ htmlHash })`), identical HTML
|
|
51
54
|
* is answered as `unchanged` without converting it again.
|
|
52
55
|
*/
|
|
53
|
-
htmlHash
|
|
56
|
+
htmlHash: string | null;
|
|
54
57
|
};
|
|
55
58
|
export type PageBody = {
|
|
56
|
-
captureIssue?:
|
|
59
|
+
captureIssue?: Nullable<CaptureIssue>;
|
|
57
60
|
outcome: FetchOutcome;
|
|
58
61
|
source: FetchSource | null;
|
|
59
|
-
markdown?: string
|
|
62
|
+
markdown?: Nullable<string>;
|
|
60
63
|
/** The HTML an origin tier read, when one did - the only source of metadata. */
|
|
61
|
-
html?: string
|
|
64
|
+
html?: Nullable<string>;
|
|
62
65
|
};
|
|
63
66
|
export type FetchPageInput = {
|
|
64
67
|
url: string;
|
|
65
68
|
/** Stored validators from the last run; a 304 returns outcome 'unchanged'. */
|
|
66
|
-
etag?: string
|
|
67
|
-
lastModified?: string
|
|
69
|
+
etag?: Nullable<string>;
|
|
70
|
+
lastModified?: Nullable<string>;
|
|
68
71
|
/** Allow the Browser Rendering tier. Default true. */
|
|
69
72
|
render?: boolean | undefined;
|
|
70
73
|
/**
|
|
71
74
|
* ISO country of the market to read the page in. A Shopify store redirects
|
|
72
|
-
* by the visitor's IP -
|
|
73
|
-
* euros and English
|
|
74
|
-
*
|
|
75
|
+
* by the visitor's IP - a worker abroad reads a Danish shop as its EU market
|
|
76
|
+
* in euros and English - and `?country=DK` pins it. Applied to the request
|
|
77
|
+
* only: the page keeps the URL it was asked for.
|
|
75
78
|
*/
|
|
76
|
-
country?: string
|
|
79
|
+
country?: Nullable<string>;
|
|
77
80
|
/** The last read's `htmlHash`; identical HTML comes back `unchanged`. */
|
|
78
|
-
htmlHash?: string
|
|
81
|
+
htmlHash?: Nullable<string>;
|
|
79
82
|
signal?: AbortSignal | undefined;
|
|
80
83
|
};
|
|
81
84
|
export type PdfParser = (input: {
|
|
@@ -115,10 +118,17 @@ export type WebsiteToMarkdownOptions = {
|
|
|
115
118
|
/** The OCR tier for a PDF Workers AI returned nothing for. */
|
|
116
119
|
parsePdf?: PdfParser;
|
|
117
120
|
/**
|
|
118
|
-
* SSRF guard run before every origin request
|
|
119
|
-
* {@link isHostAllowed}: public addresses only
|
|
121
|
+
* SSRF guard run before every origin request and every redirect it
|
|
122
|
+
* follows. Defaults to {@link isHostAllowed}: public addresses only.
|
|
120
123
|
*/
|
|
121
124
|
isHostAllowed?: (hostname: string) => Promise<boolean>;
|
|
125
|
+
/**
|
|
126
|
+
* Let the default guard and `http://` through to `localhost`, `127.0.0.1`
|
|
127
|
+
* and `::1`, e.g. outside production for a dev server. Default false.
|
|
128
|
+
*/
|
|
129
|
+
allowLocalhost?: boolean;
|
|
130
|
+
/** The `fetch` origin requests go through, behind the guard. Default the global. */
|
|
131
|
+
fetch?: Fetch;
|
|
122
132
|
};
|
|
123
133
|
/**
|
|
124
134
|
* One converter: its Cloudflare client, pools and per-host politeness limits.
|
|
@@ -136,14 +146,20 @@ export declare function createWebsiteToMarkdown(options: WebsiteToMarkdownOption
|
|
|
136
146
|
}) => Promise<PageBody>;
|
|
137
147
|
urlToMarkdown: (urlString: string) => Promise<{
|
|
138
148
|
markdown: string;
|
|
139
|
-
source:
|
|
149
|
+
source: "browser-rendering" | "content-negotiation" | "workers-ai" | "not-modified" | "firecrawl-parse";
|
|
140
150
|
}>;
|
|
141
151
|
convertFileWithWorkersAi: (input: import("./client.js").ConvertFileInput) => Promise<string | null>;
|
|
142
152
|
renderUrlToMarkdown: (url: string, signal?: AbortSignal) => Promise<string | null>;
|
|
143
153
|
/** The SSRF guard the converter runs, bound to its `logger`. */
|
|
144
154
|
isHostAllowed: (hostname: string) => Promise<boolean>;
|
|
155
|
+
/**
|
|
156
|
+
* `fetch` behind the converter's guard, run on the URL and on every
|
|
157
|
+
* redirect. Throws `HostNotAllowedError` when one is refused. Use it for
|
|
158
|
+
* any other request to a customer-supplied URL.
|
|
159
|
+
*/
|
|
160
|
+
guardedFetch(input: string | URL, init?: RequestInit, fetchOptions?: Pick<GuardedFetchOptions, "maxRedirects">): Promise<Response>;
|
|
145
161
|
/** `withCrawlSnapshot` bound to the `crawlSnapshot` option. */
|
|
146
|
-
withCrawlSnapshot<T>(input: CrawlSnapshotInput
|
|
162
|
+
withCrawlSnapshot<T>(input: Nullable<CrawlSnapshotInput>, run: () => Promise<T>): Promise<T>;
|
|
147
163
|
};
|
|
148
164
|
export type WebsiteToMarkdown = ReturnType<typeof createWebsiteToMarkdown>;
|
|
149
165
|
//# sourceMappingURL=fetch-page.d.ts.map
|
package/dist/fetch-page.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fetch-page.d.ts","sourceRoot":"","sources":["../src/fetch-page.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"fetch-page.d.ts","sourceRoot":"","sources":["../src/fetch-page.ts"],"names":[],"mappings":"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"}
|