notion-static-exporter 1.0.2

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,11 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ - Initial release: Puppeteer BFS scrape of public `notion.site` pages
6
+ - Freezes live Notion UI (not admin HTML-export styling)
7
+ - Cloudflare handling: wait for clearance, persistent Chrome profile, delays,
8
+ retries, SPA clicks for subpages
9
+ - Downloads CSS/fonts/images; strips client JS for offline static hosting
10
+ - CLI: `sync`, `trigger`, `init-config`, `help`
11
+ - Sample GitHub Actions workflow (cron every 4h + workflow_dispatch)
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 novailoveyou
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,223 @@
1
+ # notion-static-exporter
2
+
3
+ Scrape a **public** Notion site (`*.notion.site`) into a static mirror that looks
4
+ like the **live Notion UI** — not Notion’s admin “Export → HTML” look.
5
+
6
+ Compare:
7
+
8
+ - Live UI (target): https://almond-brownie-c82.notion.site/Elementary-3b515e0e4a098053bb74c985cebfd777
9
+ - Old export style (different UI): https://elementary.english.orlov.app/
10
+
11
+ This tool freezes the painted Notion SPA (HTML + CSS + fonts + images), strips
12
+ client JS that would blank the page offline, rewrites links, and writes files
13
+ you can host on GitHub Pages (installable PWA with offline cache).
14
+
15
+ No Notion API token. Exhaustive BFS: every same-site link on every page is
16
+ queued until the tree is done.
17
+
18
+ ```bash
19
+ bunx notion-static-exporter sync \
20
+ --url "https://almond-brownie-c82.notion.site/Elementary-3b515e0e4a098053bb74c985cebfd777" \
21
+ --out ./_site
22
+ ```
23
+
24
+ ## Publishing
25
+
26
+ Publish this package to npm so Pages repos can call it from GitHub Actions:
27
+
28
+ ```bash
29
+ # one-time: login to npm (browser or token)
30
+ npm login
31
+ # or: npm config set //registry.npmjs.org/:_authToken=npm_…
32
+
33
+ bun run build
34
+ bun run publish:prod # → bun publish --access public
35
+ ```
36
+
37
+ That publishes `notion-static-exporter` (version from `package.json`). After that,
38
+ anywhere can run:
39
+
40
+ ```bash
41
+ npx notion-static-exporter sync --url "…" --out .
42
+ # or
43
+ bunx notion-static-exporter sync --url "…" --out .
44
+ ```
45
+
46
+ Bump `version` in `package.json` before each publish if the previous version is
47
+ already on the registry.
48
+
49
+ ## Usage
50
+
51
+ ### Install / run locally
52
+
53
+ ```bash
54
+ bunx notion-static-exporter sync --url "https://….notion.site/…" --out ./_site
55
+ bun add -g notion-static-exporter
56
+ notion-static-exporter sync --url "…" --out ./_site
57
+ ```
58
+
59
+ Optional config `notion-static-exporter.config.json`:
60
+
61
+ ```json
62
+ {
63
+ "url": "https://almond-brownie-c82.notion.site/Elementary-3b515e0e4a098053bb74c985cebfd777",
64
+ "out": "./_site",
65
+ "keepCname": true,
66
+ "maxPages": 0
67
+ }
68
+ ```
69
+
70
+ ```bash
71
+ bunx notion-static-exporter init-config
72
+ ```
73
+
74
+ ### Wire each GitHub Pages repo
75
+
76
+ Use the ready template: [`examples/sync-notion.yml`](examples/sync-notion.yml).
77
+
78
+ #### [elementary.english.orlov.app](https://github.com/Novailoveyou/elementary.english.orlov.app)
79
+
80
+ 1. Add `.github/workflows/sync-notion.yml` (copy from the example).
81
+ 2. Repo **Settings → Secrets and variables → Actions → Variables**:
82
+ - `NOTION_URL` = `https://almond-brownie-c82.notion.site/Elementary-3b515e0e4a098053bb74c985cebfd777`
83
+ 3. Ensure **GitHub Pages** serves from `main` (root or `/docs` — whatever you use now).
84
+ 4. Keep existing `CNAME` — the workflow uses `--keep-cname`.
85
+ 5. Remove / stop the old `publish.yml` (notion4ever + `NOTION_TOKEN`) so you don’t have two deployers fighting.
86
+
87
+ #### [english.orlov.app](https://github.com/Novailoveyou/english.orlov.app)
88
+
89
+ Same workflow file, different variable:
90
+
91
+ - `NOTION_URL` = `https://almond-brownie-c82.notion.site/Elena-Pilip-61ff5f4c44a34429944ea190ee690cc9`
92
+ (drop the `#…` hash for the crawl root)
93
+
94
+ No Notion API token needed — public `*.notion.site` only.
95
+
96
+ ### What the Action does
97
+
98
+ On schedule (every 4h in the example) or **Actions → Sync Notion → Run workflow**:
99
+
100
+ 1. Checkout the Pages repo
101
+ 2. Install Chrome + Bun
102
+ 3. Cache Chrome profile (Cloudflare cookies)
103
+ 4. `bunx notion-static-exporter sync --url "$NOTION_URL" --out . --keep-cname …`
104
+ 5. Commit & push if anything changed → Pages updates
105
+
106
+ The workflow also:
107
+
108
+ - Points Puppeteer at Chrome via `PUPPETEER_EXECUTABLE_PATH`
109
+ - Runs headless with container-safe flags (`--no-sandbox`, `--disable-dev-shm-usage`, …)
110
+ - Uses lower concurrency (`2`) to fit runner memory
111
+
112
+ Local override for a custom Chrome binary:
113
+
114
+ ```bash
115
+ PUPPETEER_EXECUTABLE_PATH=/usr/bin/google-chrome \
116
+ bunx notion-static-exporter sync --url "…" --out ./_site
117
+ ```
118
+
119
+ ### Trigger from this machine
120
+
121
+ After the workflow exists:
122
+
123
+ ```bash
124
+ export GITHUB_TOKEN=ghp_… # workflow scope
125
+ bunx notion-static-exporter trigger \
126
+ --repo Novailoveyou/elementary.english.orlov.app \
127
+ --workflow sync-notion.yml \
128
+ --ref main
129
+ ```
130
+
131
+ Same for `english.orlov.app`.
132
+
133
+ ### Order of operations
134
+
135
+ | Step | Where |
136
+ |------|--------|
137
+ | 1. `bun run publish:prod` | `notion-static-exporter` |
138
+ | 2. Add workflow + `NOTION_URL` | each Pages repo |
139
+ | 3. Disable old notion4ever `publish.yml` | each Pages repo |
140
+ | 4. Manual “Run workflow” once | verify scrape + commit |
141
+ | 5. Leave schedule on | ongoing sync |
142
+
143
+ ## Commands
144
+
145
+ | Command | Purpose |
146
+ |--------|---------|
147
+ | `sync` | Crawl public URL → write static files (default) |
148
+ | `restore` | Restore `--out` from the previous `.nsp-backup` |
149
+ | `trigger` | `workflow_dispatch` on a consumer GitHub repo |
150
+ | `help` | Usage |
151
+
152
+ ### Sync flags
153
+
154
+ - `--url` / `NOTION_URL` — public Notion root
155
+ - `--out` / `OUT_DIR` — output directory (default `.`)
156
+ - `--keep-cname` — keep existing `CNAME` across sync
157
+ - `--max-pages <n>` — safety cap (`0` = unlimited)
158
+ - `--delay-ms <n>` — pause between pages (default `1500`)
159
+ - `--concurrency <n>` — parallel browser tabs (default `3`)
160
+ - `--retries <n>` — retries when Cloudflare blocks (default `3`)
161
+ - `--user-data-dir <path>` — Chrome profile (keeps CF cookies)
162
+ - `--headed` — show Chromium (helps pass hard challenges)
163
+ - `--full` — ignore fingerprints; re-freeze every page
164
+
165
+ ### Resync / cache
166
+
167
+ Sync builds into a staging folder, then swaps into `--out` and keeps **one**
168
+ backup (`--out.nsp-backup`). Unchanged pages (content fingerprint match) skip
169
+ freeze/asset download. Use `--full` to force a complete re-scrape. On failure
170
+ the live `--out` is left untouched.
171
+
172
+ ```bash
173
+ bunx notion-static-exporter restore --out ./_site
174
+ ```
175
+
176
+ ## Cloudflare
177
+
178
+ Notion’s public sites sit behind Cloudflare (“Just a moment…”). The crawler:
179
+
180
+ 1. Uses a **persistent Chrome profile** (`~/.notion-static-exporter/chrome-profile`) so `cf_clearance` cookies stick across runs
181
+ 2. **Waits** for challenges to clear before saving a page
182
+ 3. **Delays** between page loads (`--delay-ms`)
183
+ 4. **Retries** blocked pages (`--retries`, default 3)
184
+ 5. Never saves a challenge interstitial as content
185
+
186
+ If challenges still stick in headless CI, warm the profile once locally:
187
+
188
+ ```bash
189
+ bunx notion-static-exporter sync --url "…" --out ./_site --headed --max-pages 5
190
+ ```
191
+
192
+ Then re-run headless using the same `--user-data-dir` (or let Actions restore the cached profile).
193
+
194
+ ## How it works
195
+
196
+ 1. Launch Chromium (Puppeteer) — system Chrome, `PUPPETEER_EXECUTABLE_PATH`, or bundled
197
+ 2. Open each public page and wait for the **live Notion UI** to paint
198
+ 3. Collect **all** same-origin page links → BFS queue
199
+ 4. Download CSS / fonts / images into `assets/`
200
+ 5. **Freeze** the rendered DOM (inline accessible CSS, remove Notion client JS)
201
+ 6. Rewrite links to local `.html` files + PWA service worker
202
+ 7. Publish via staging → backup → swap
203
+
204
+ CLI shows an animated spinner, a progress bar (`done/known`, grows as the queue
205
+ discovers pages), and rotating status text for the current phase (navigate,
206
+ Cloudflare, views, assets, freeze, …). In GitHub Actions (no TTY) it prints
207
+ phase lines every few seconds instead.
208
+
209
+ ## Library
210
+
211
+ ```ts
212
+ import { syncNotionSite, triggerWorkflow } from "notion-static-exporter";
213
+
214
+ await syncNotionSite({
215
+ url: "https://….notion.site/…",
216
+ out: "./_site",
217
+ keepCname: true,
218
+ });
219
+ ```
220
+
221
+ ## License
222
+
223
+ MIT
@@ -0,0 +1,47 @@
1
+ import type { HTTPResponse, Page } from "puppeteer";
2
+ export type AssetStore = {
3
+ map: Map<string, string>;
4
+ /** relative asset path → source URL(s) */
5
+ reverse: Map<string, string>;
6
+ dir: string;
7
+ outRoot: string;
8
+ };
9
+ export declare function createAssetStore(outRoot: string): AssetStore;
10
+ export declare function normalizeAssetKey(url: string): string;
11
+ /** Notion page URLs must never be saved as static assets. */
12
+ export declare function isNotionPageAssetUrl(url: string): boolean;
13
+ export declare function saveAsset(store: AssetStore, url: string, body: Buffer, contentType: string | null): string | null;
14
+ export declare function downloadUrl(store: AssetStore, url: string, page: Page): Promise<string | null>;
15
+ export declare function attachResponseCollector(page: Page): {
16
+ urls: Set<string>;
17
+ responses: Map<string, HTTPResponse>;
18
+ detach: () => void;
19
+ };
20
+ /** Persist bodies Chrome already fetched (best source for images/fonts). */
21
+ export declare function saveCollectedResponses(store: AssetStore, responses: Map<string, HTTPResponse>): Promise<void>;
22
+ export declare function downloadAssetUrls(store: AssetStore, page: Page, urls: Iterable<string>, concurrency?: number): Promise<void>;
23
+ export declare function collectDomAssetUrls(page: Page): Promise<string[]>;
24
+ export declare function rewriteHtml(html: string, pageLocalPath: string, pageUrl: string, store: AssetStore, pageUrlMap: Map<string, string>): string;
25
+ /** Fix <a href="#"> using the enclosing element's data-block-id when it matches a scraped page. */
26
+ export declare function repairDeadPageLinks(html: string, pageUrlMap: Map<string, string>, pageLocalPath: string): string;
27
+ export declare function rewriteCssFiles(store: AssetStore, pageUrl: string): void;
28
+ export declare function findRemainingRemoteUrls(html: string, baseUrl?: string): string[];
29
+ /** Map Notion block UUID (32-hex) → best local audio/image asset path. */
30
+ export declare function buildBlockAssetIndex(entries: Iterable<[string, string]>): Map<string, {
31
+ audio?: string;
32
+ image?: string;
33
+ imageWidth: number;
34
+ }>;
35
+ /**
36
+ * Inject <audio>/<img> into empty Notion media shells and replace gif
37
+ * placeholders using assets keyed by ?id=<blockId> in the CDN URL.
38
+ */
39
+ export declare function injectBlockMedia(html: string, store: AssetStore, pageLocalPath: string): string;
40
+ /**
41
+ * Replace 1×1 gif placeholders in Notion bookmark cards with the target
42
+ * page's og:image (downloaded locally). Generic — works for any future
43
+ * bookmark links, not just Wordwall.
44
+ */
45
+ export declare function enrichBookmarkCovers(html: string, store: AssetStore, pageLocalPath: string): Promise<string>;
46
+ export declare function ensureParentDir(filePath: string): void;
47
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../src/assets.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { type Browser, type Page } from "puppeteer";
2
+ export type LaunchBrowserOptions = {
3
+ headless?: boolean;
4
+ /** Persist cookies / CF clearance across runs */
5
+ userDataDir?: string;
6
+ };
7
+ export declare function defaultUserDataDir(): string;
8
+ export declare function launchBrowser(opts?: LaunchBrowserOptions): Promise<Browser>;
9
+ export declare function preparePage(browser: Browser): Promise<Page>;
10
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAGA,OAAkB,EAAE,KAAK,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AAG/D,MAAM,MAAM,oBAAoB,GAAG;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AA6CD,wBAAsB,aAAa,CACjC,IAAI,GAAE,oBAAyB,GAC9B,OAAO,CAAC,OAAO,CAAC,CA4ClB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAcjE"}
@@ -0,0 +1,54 @@
1
+ import type { Page } from "puppeteer";
2
+ import type { AssetStore } from "./assets.ts";
3
+ export declare const CACHE_VERSION: 1;
4
+ export declare const CACHE_FILE = ".nsp-cache.json";
5
+ export type PageCacheEntry = {
6
+ url: string;
7
+ file: string;
8
+ fingerprint: string;
9
+ links: string[];
10
+ };
11
+ export type SyncCache = {
12
+ version: typeof CACHE_VERSION;
13
+ rootUrl: string;
14
+ pages: Record<string, PageCacheEntry>;
15
+ /** Normalized asset URL → relative path under site root (e.g. assets/abc.png) */
16
+ assets: Record<string, string>;
17
+ };
18
+ export declare function stagingDir(outRoot: string): string;
19
+ export declare function backupDir(outRoot: string): string;
20
+ export declare function emptyCache(rootUrl: string): SyncCache;
21
+ export declare function loadCache(dir: string): SyncCache | null;
22
+ export declare function saveCache(dir: string, cache: SyncCache): void;
23
+ /** Seed asset store from a previous cache so downloads/rewrites can reuse files. */
24
+ export declare function hydrateAssetStore(store: AssetStore, cache: SyncCache): number;
25
+ export declare function snapshotAssets(store: AssetStore): Record<string, string>;
26
+ /**
27
+ * Light content fingerprint after Notion has rendered.
28
+ * Avoids re-freezing / re-downloading when the page body is unchanged.
29
+ */
30
+ export declare function pageFingerprint(page: Page): Promise<string>;
31
+ export declare function siteHasContent(dir: string): boolean;
32
+ /**
33
+ * Prepare a clean staging directory.
34
+ * Copies assets + cache + a few root files (fast). HTML is copied on demand
35
+ * for cache hits, or written fresh when a page is re-scraped.
36
+ */
37
+ export declare function prepareStaging(outRoot: string, keepCname?: boolean): {
38
+ staging: string;
39
+ copied: boolean;
40
+ };
41
+ /** Ensure a previously scraped HTML page exists in staging (for cache hits). */
42
+ export declare function ensureCachedPageInStaging(outRoot: string, staging: string, file: string): boolean;
43
+ /**
44
+ * Atomically publish staging → outRoot, keeping one previous copy at backup.
45
+ * Live outRoot is never wiped until staging is complete.
46
+ */
47
+ export declare function publishStaging(outRoot: string): {
48
+ backup: string | null;
49
+ };
50
+ /** Restore the single backup over outRoot (if present). */
51
+ export declare function restoreBackup(outRoot: string): boolean;
52
+ /** Remove HTML pages in staging that were not part of this crawl. */
53
+ export declare function pruneOrphanHtml(staging: string, keepFiles: Set<string>): number;
54
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAG9C,eAAO,MAAM,aAAa,EAAG,CAAU,CAAC;AACxC,eAAO,MAAM,UAAU,oBAAoB,CAAC;AAE5C,MAAM,MAAM,cAAc,GAAG;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,OAAO,aAAa,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACtC,iFAAiF;IACjF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAChC,CAAC;AAEF,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAErD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAWvD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAG7D;AAED,oFAAoF;AACpF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAa7E;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAuBjE;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAUnD;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,EACf,SAAS,CAAC,EAAE,OAAO,GAClB;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,CAsCtC;AAED,gFAAgF;AAChF,wBAAgB,yBAAyB,CACvC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,OAAO,CAQT;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CA+BzE;AAED,2DAA2D;AAC3D,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CA4BtD;AAED,qEAAqE;AACrE,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,GAAG,CAAC,MAAM,CAAC,GACrB,MAAM,CASR"}
@@ -0,0 +1,23 @@
1
+ import type { Page } from "puppeteer";
2
+ /** Cloudflare / bot interstitial ("Just a moment..."). */
3
+ export declare function isChallengePage(page: Page): Promise<boolean>;
4
+ /**
5
+ * Wait until Cloudflare clears (cf_clearance / title change) or timeout.
6
+ * Returns true if the page looks clear of the challenge.
7
+ */
8
+ export declare function waitOutChallenge(page: Page, timeoutMs?: number): Promise<boolean>;
9
+ export declare function sleep(ms: number): Promise<void>;
10
+ /** Human-like pause between navigations. Uses small jitter; skip when baseMs ≤ 0. */
11
+ export declare function crawlDelay(baseMs: number): Promise<void>;
12
+ /**
13
+ * Global spacing between navigations across workers (avoids N×delay with concurrency).
14
+ */
15
+ export declare function createNavGate(baseMs: number): () => Promise<void>;
16
+ /**
17
+ * Navigate to a Notion page. Prefer in-app link click (SPA) to avoid
18
+ * re-triggering Cloudflare; fall back to full goto.
19
+ */
20
+ export declare function navigateToPage(page: Page, url: string, opts?: {
21
+ allowSpaClick?: boolean;
22
+ }): Promise<void>;
23
+ //# sourceMappingURL=challenge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"challenge.d.ts","sourceRoot":"","sources":["../src/challenge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAItC,0DAA0D;AAC1D,wBAAsB,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAqBlE;AAED;;;GAGG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,IAAI,EACV,SAAS,SAAS,GACjB,OAAO,CAAC,OAAO,CAAC,CAyBlB;AAED,wBAAsB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErD;AAED,qFAAqF;AACrF,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAUjE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,GAAG,EAAE,MAAM,EACX,IAAI,GAAE;IAAE,aAAa,CAAC,EAAE,OAAO,CAAA;CAAO,GACrC,OAAO,CAAC,IAAI,CAAC,CAsCf"}