Skip to content

Commit 7b3cae1

Browse files
committed
fix(firecrawl): preserve index pages with crawl path filters
1 parent eb1f273 commit 7b3cae1

2 files changed

Lines changed: 41 additions & 1 deletion

File tree

‎apps/sim/tools/firecrawl/crawl.test.ts‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,24 @@ describe('Firecrawl legacy crawl depth', () => {
4848
expect(patterns.some((pattern) => pattern.test('/docs'))).toBe(false)
4949
})
5050

51+
it.each<[number, string, boolean]>([
52+
[0, '/index.html', false],
53+
[0, '/index.php/', false],
54+
[1, '/docs/index.html', false],
55+
[1, '/docs/index.php', false],
56+
[1, '/docs//index.html/', false],
57+
[1, '/docs/guide/index.html', true],
58+
[1, '/docs/index.html/child', true],
59+
[1, '/docs/index.html.bak', true],
60+
[1, '/docs/index.phpx', true],
61+
[1, '/docs/inde', true],
62+
[1, '/docs/index.htm', true],
63+
])('applies depth %s with existing includes to %s', (maxDepth, path, excluded) => {
64+
const body = crawlBody({ maxDepth, includePaths: ['^/'] })
65+
const patterns = (body.excludePaths as string[]).map((pattern) => new RegExp(pattern))
66+
expect(patterns.some((pattern) => pattern.test(path))).toBe(excluded)
67+
})
68+
5169
it.each([-1, 1.5, Number.NaN, Number.POSITIVE_INFINITY])(
5270
'rejects an invalid legacy depth: %s',
5371
(maxDepth) => {

‎apps/sim/tools/firecrawl/crawl.ts‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { sleep } from '@sim/utils/helpers'
3+
import { escapeRegExp } from '@sim/utils/string'
34
import { DEFAULT_EXECUTION_TIMEOUT_MS } from '@/lib/core/execution-limits'
45
import { firecrawlHosting } from '@/tools/firecrawl/hosting'
56
import {
@@ -163,7 +164,28 @@ export const crawlTool: ToolConfig<FirecrawlCrawlParams, FirecrawlCrawlResponse>
163164
// v1 depth counted URL path segments; v2 discovery depth counts link hops.
164165
body.regexOnFullURL = false
165166
if (params.includePaths?.length) {
166-
body.excludePaths = [...(params.excludePaths ?? []), `^(/+[^/]+){${maxDepth + 1}}`]
167+
const indexFiles = ['index.html', 'index.php']
168+
const nonIndexSegmentPatterns = new Set<string>()
169+
// Rust regex has no lookarounds, so enumerate incomplete prefixes and first mismatches.
170+
for (const file of indexFiles) {
171+
for (let length = 0; length <= file.length; length++) {
172+
const prefix = file.slice(0, length)
173+
const nextCharacters = indexFiles
174+
.filter((indexFile) => indexFile.startsWith(prefix))
175+
.map((indexFile) => indexFile.charAt(length))
176+
.join('')
177+
const escapedPrefix = escapeRegExp(prefix)
178+
if (prefix && !indexFiles.includes(prefix)) {
179+
nonIndexSegmentPatterns.add(escapedPrefix)
180+
}
181+
nonIndexSegmentPatterns.add(`${escapedPrefix}[^/${nextCharacters}][^/]*`)
182+
}
183+
}
184+
const nonIndexBoundary = `^(/+[^/]+){${maxDepth}}/+(${[...nonIndexSegmentPatterns].join('|')})/*$`
185+
body.excludePaths = [
186+
...(params.excludePaths ?? []),
187+
`^(/+[^/]+){${maxDepth + 2}}|${nonIndexBoundary}`,
188+
]
167189
} else {
168190
body.includePaths = [
169191
`^(/+[^/]+){0,${maxDepth}}/*$`,

0 commit comments

Comments
 (0)