afterburn-cli 1.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/LICENSE +21 -0
- package/README.md +281 -0
- package/dist/ai/gemini-client.d.ts +21 -0
- package/dist/ai/gemini-client.js +105 -0
- package/dist/ai/gemini-client.js.map +1 -0
- package/dist/ai/index.d.ts +1 -0
- package/dist/ai/index.js +3 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/analysis/diagnosis-schema.d.ts +106 -0
- package/dist/analysis/diagnosis-schema.js +54 -0
- package/dist/analysis/diagnosis-schema.js.map +1 -0
- package/dist/analysis/error-analyzer.d.ts +9 -0
- package/dist/analysis/error-analyzer.js +573 -0
- package/dist/analysis/error-analyzer.js.map +1 -0
- package/dist/analysis/index.d.ts +4 -0
- package/dist/analysis/index.js +6 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/source-mapper.d.ts +19 -0
- package/dist/analysis/source-mapper.js +329 -0
- package/dist/analysis/source-mapper.js.map +1 -0
- package/dist/analysis/ui-auditor.d.ts +9 -0
- package/dist/analysis/ui-auditor.js +104 -0
- package/dist/analysis/ui-auditor.js.map +1 -0
- package/dist/artifacts/artifact-storage.d.ts +44 -0
- package/dist/artifacts/artifact-storage.js +99 -0
- package/dist/artifacts/artifact-storage.js.map +1 -0
- package/dist/artifacts/index.d.ts +1 -0
- package/dist/artifacts/index.js +3 -0
- package/dist/artifacts/index.js.map +1 -0
- package/dist/browser/browser-manager.d.ts +45 -0
- package/dist/browser/browser-manager.js +88 -0
- package/dist/browser/browser-manager.js.map +1 -0
- package/dist/browser/challenge-detector.d.ts +10 -0
- package/dist/browser/challenge-detector.js +58 -0
- package/dist/browser/challenge-detector.js.map +1 -0
- package/dist/browser/cookie-dismisser.d.ts +18 -0
- package/dist/browser/cookie-dismisser.js +76 -0
- package/dist/browser/cookie-dismisser.js.map +1 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +6 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/stealth-browser.d.ts +13 -0
- package/dist/browser/stealth-browser.js +59 -0
- package/dist/browser/stealth-browser.js.map +1 -0
- package/dist/cli/commander-cli.d.ts +2 -0
- package/dist/cli/commander-cli.js +150 -0
- package/dist/cli/commander-cli.js.map +1 -0
- package/dist/cli/doctor.d.ts +34 -0
- package/dist/cli/doctor.js +124 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/first-run.d.ts +6 -0
- package/dist/cli/first-run.js +58 -0
- package/dist/cli/first-run.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/progress.d.ts +11 -0
- package/dist/cli/progress.js +30 -0
- package/dist/cli/progress.js.map +1 -0
- package/dist/core/engine.d.ts +33 -0
- package/dist/core/engine.js +269 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/validation.d.ts +52 -0
- package/dist/core/validation.js +228 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/discovery/crawler.d.ts +58 -0
- package/dist/discovery/crawler.js +240 -0
- package/dist/discovery/crawler.js.map +1 -0
- package/dist/discovery/discovery-pipeline.d.ts +22 -0
- package/dist/discovery/discovery-pipeline.js +256 -0
- package/dist/discovery/discovery-pipeline.js.map +1 -0
- package/dist/discovery/element-mapper.d.ts +21 -0
- package/dist/discovery/element-mapper.js +422 -0
- package/dist/discovery/element-mapper.js.map +1 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.js +8 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/link-validator.d.ts +15 -0
- package/dist/discovery/link-validator.js +137 -0
- package/dist/discovery/link-validator.js.map +1 -0
- package/dist/discovery/sitemap-builder.d.ts +19 -0
- package/dist/discovery/sitemap-builder.js +166 -0
- package/dist/discovery/sitemap-builder.js.map +1 -0
- package/dist/discovery/spa-detector.d.ts +12 -0
- package/dist/discovery/spa-detector.js +271 -0
- package/dist/discovery/spa-detector.js.map +1 -0
- package/dist/execution/error-detector.d.ts +10 -0
- package/dist/execution/error-detector.js +87 -0
- package/dist/execution/error-detector.js.map +1 -0
- package/dist/execution/evidence-capture.d.ts +8 -0
- package/dist/execution/evidence-capture.js +37 -0
- package/dist/execution/evidence-capture.js.map +1 -0
- package/dist/execution/index.d.ts +5 -0
- package/dist/execution/index.js +7 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/step-handlers.d.ts +48 -0
- package/dist/execution/step-handlers.js +349 -0
- package/dist/execution/step-handlers.js.map +1 -0
- package/dist/execution/test-data.d.ts +50 -0
- package/dist/execution/test-data.js +160 -0
- package/dist/execution/test-data.js.map +1 -0
- package/dist/execution/workflow-executor.d.ts +56 -0
- package/dist/execution/workflow-executor.js +331 -0
- package/dist/execution/workflow-executor.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/entry.d.ts +2 -0
- package/dist/mcp/entry.js +5 -0
- package/dist/mcp/entry.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +19 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +162 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/planning/heuristic-planner.d.ts +7 -0
- package/dist/planning/heuristic-planner.js +238 -0
- package/dist/planning/heuristic-planner.js.map +1 -0
- package/dist/planning/index.d.ts +3 -0
- package/dist/planning/index.js +5 -0
- package/dist/planning/index.js.map +1 -0
- package/dist/planning/plan-schema.d.ts +74 -0
- package/dist/planning/plan-schema.js +39 -0
- package/dist/planning/plan-schema.js.map +1 -0
- package/dist/planning/workflow-planner.d.ts +39 -0
- package/dist/planning/workflow-planner.js +211 -0
- package/dist/planning/workflow-planner.js.map +1 -0
- package/dist/reports/health-scorer.d.ts +14 -0
- package/dist/reports/health-scorer.js +88 -0
- package/dist/reports/health-scorer.js.map +1 -0
- package/dist/reports/html-generator.d.ts +10 -0
- package/dist/reports/html-generator.js +155 -0
- package/dist/reports/html-generator.js.map +1 -0
- package/dist/reports/index.d.ts +4 -0
- package/dist/reports/index.js +6 -0
- package/dist/reports/index.js.map +1 -0
- package/dist/reports/markdown-generator.d.ts +10 -0
- package/dist/reports/markdown-generator.js +334 -0
- package/dist/reports/markdown-generator.js.map +1 -0
- package/dist/reports/priority-ranker.d.ts +22 -0
- package/dist/reports/priority-ranker.js +608 -0
- package/dist/reports/priority-ranker.js.map +1 -0
- package/dist/screenshots/dual-format.d.ts +14 -0
- package/dist/screenshots/dual-format.js +59 -0
- package/dist/screenshots/dual-format.js.map +1 -0
- package/dist/screenshots/index.d.ts +2 -0
- package/dist/screenshots/index.js +4 -0
- package/dist/screenshots/index.js.map +1 -0
- package/dist/screenshots/screenshot-manager.d.ts +33 -0
- package/dist/screenshots/screenshot-manager.js +86 -0
- package/dist/screenshots/screenshot-manager.js.map +1 -0
- package/dist/testing/accessibility-auditor.d.ts +23 -0
- package/dist/testing/accessibility-auditor.js +44 -0
- package/dist/testing/accessibility-auditor.js.map +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/meta-auditor.d.ts +16 -0
- package/dist/testing/meta-auditor.js +268 -0
- package/dist/testing/meta-auditor.js.map +1 -0
- package/dist/testing/performance-monitor.d.ts +15 -0
- package/dist/testing/performance-monitor.js +64 -0
- package/dist/testing/performance-monitor.js.map +1 -0
- package/dist/types/artifacts.d.ts +58 -0
- package/dist/types/artifacts.js +3 -0
- package/dist/types/artifacts.js.map +1 -0
- package/dist/types/discovery.d.ts +124 -0
- package/dist/types/discovery.js +3 -0
- package/dist/types/discovery.js.map +1 -0
- package/dist/types/execution.d.ts +154 -0
- package/dist/types/execution.js +3 -0
- package/dist/types/execution.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/sanitizer.d.ts +25 -0
- package/dist/utils/sanitizer.js +98 -0
- package/dist/utils/sanitizer.js.map +1 -0
- package/package.json +86 -0
- package/templates/report.hbs +202 -0
- package/templates/styles/report.css +607 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
// Interactive element discovery including hidden elements (modals, dropdowns, mobile nav)
|
|
2
|
+
// Destructive action denylist - skip elements with these keywords
|
|
3
|
+
const DESTRUCTIVE_KEYWORDS = [
|
|
4
|
+
'delete',
|
|
5
|
+
'remove',
|
|
6
|
+
'destroy',
|
|
7
|
+
'reset',
|
|
8
|
+
'clear',
|
|
9
|
+
'drop',
|
|
10
|
+
'purge',
|
|
11
|
+
'revoke',
|
|
12
|
+
'terminate',
|
|
13
|
+
'unsubscribe',
|
|
14
|
+
'cancel-account',
|
|
15
|
+
'close-account',
|
|
16
|
+
'logout',
|
|
17
|
+
'log out',
|
|
18
|
+
'sign out',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Check if text matches destructive action patterns
|
|
22
|
+
*/
|
|
23
|
+
function isDestructiveAction(text) {
|
|
24
|
+
const lowerText = text.toLowerCase().trim();
|
|
25
|
+
return DESTRUCTIVE_KEYWORDS.some(keyword => lowerText.includes(keyword));
|
|
26
|
+
}
|
|
27
|
+
function escapeSelectorText(value) {
|
|
28
|
+
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Discover all visible interactive elements on a page
|
|
32
|
+
*/
|
|
33
|
+
export async function discoverElements(page, pageUrl) {
|
|
34
|
+
const elements = {
|
|
35
|
+
forms: [],
|
|
36
|
+
buttons: [],
|
|
37
|
+
links: [],
|
|
38
|
+
menus: [],
|
|
39
|
+
otherInteractive: [],
|
|
40
|
+
};
|
|
41
|
+
const pageHostname = new URL(pageUrl).hostname;
|
|
42
|
+
// Discover all forms with field inventory
|
|
43
|
+
const forms = await page.locator('form').all();
|
|
44
|
+
for (let i = 0; i < forms.length; i++) {
|
|
45
|
+
const form = forms[i];
|
|
46
|
+
try {
|
|
47
|
+
const action = (await form.getAttribute('action')) || '';
|
|
48
|
+
const method = ((await form.getAttribute('method')) || 'GET').toUpperCase();
|
|
49
|
+
// Build selector for the form
|
|
50
|
+
const formId = await form.getAttribute('id');
|
|
51
|
+
const formName = await form.getAttribute('name');
|
|
52
|
+
let selector = 'form';
|
|
53
|
+
if (formId) {
|
|
54
|
+
selector = `form#${formId}`;
|
|
55
|
+
}
|
|
56
|
+
else if (formName) {
|
|
57
|
+
selector = `form[name="${formName}"]`;
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
selector = `form:nth-of-type(${i + 1})`;
|
|
61
|
+
}
|
|
62
|
+
// Resolve action URL
|
|
63
|
+
const resolvedAction = action ? new URL(action, pageUrl).href : pageUrl;
|
|
64
|
+
// Find all input, textarea, select within the form
|
|
65
|
+
const fields = await form.locator('input, textarea, select').all();
|
|
66
|
+
const formFields = [];
|
|
67
|
+
for (const field of fields) {
|
|
68
|
+
try {
|
|
69
|
+
const type = (await field.getAttribute('type')) || 'text';
|
|
70
|
+
const name = (await field.getAttribute('name')) || '';
|
|
71
|
+
const required = (await field.getAttribute('required')) !== null;
|
|
72
|
+
const placeholder = (await field.getAttribute('placeholder')) || '';
|
|
73
|
+
// Try to find associated label
|
|
74
|
+
let label = (await field.getAttribute('aria-label')) || '';
|
|
75
|
+
if (!label) {
|
|
76
|
+
const fieldId = await field.getAttribute('id');
|
|
77
|
+
if (fieldId) {
|
|
78
|
+
// Look for label with matching "for" attribute
|
|
79
|
+
const labelElement = await page.locator(`label[for="${fieldId}"]`).first();
|
|
80
|
+
const labelText = await labelElement.textContent().catch(() => '');
|
|
81
|
+
label = labelText?.trim() || '';
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// If still no label, check if field is inside a label
|
|
85
|
+
if (!label) {
|
|
86
|
+
const parentLabel = await field.locator('xpath=ancestor::label[1]').first();
|
|
87
|
+
const labelText = await parentLabel.textContent().catch(() => '');
|
|
88
|
+
label = labelText?.trim() || '';
|
|
89
|
+
}
|
|
90
|
+
formFields.push({
|
|
91
|
+
type,
|
|
92
|
+
name,
|
|
93
|
+
label,
|
|
94
|
+
required,
|
|
95
|
+
placeholder,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// Failed to extract field info, skip
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
elements.forms.push({
|
|
103
|
+
action: resolvedAction,
|
|
104
|
+
method,
|
|
105
|
+
selector,
|
|
106
|
+
fields: formFields,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Failed to extract form info, skip
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Discover all buttons
|
|
114
|
+
const buttons = await page.getByRole('button').all();
|
|
115
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
116
|
+
const button = buttons[i];
|
|
117
|
+
try {
|
|
118
|
+
const text = (await button.textContent()) || '';
|
|
119
|
+
const ariaLabel = (await button.getAttribute('aria-label')) || '';
|
|
120
|
+
const type = (await button.getAttribute('type')) || 'button';
|
|
121
|
+
const disabled = (await button.getAttribute('disabled')) !== null;
|
|
122
|
+
const isVisible = await button.isVisible();
|
|
123
|
+
// Build selector - prefer has-text when text is unique
|
|
124
|
+
let selector = 'button';
|
|
125
|
+
if (text.trim()) {
|
|
126
|
+
selector = `button:has-text("${escapeSelectorText(text.trim())}")`;
|
|
127
|
+
}
|
|
128
|
+
else if (ariaLabel) {
|
|
129
|
+
selector = `button[aria-label="${escapeSelectorText(ariaLabel)}"]`;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Fallback to nth-of-type
|
|
133
|
+
selector = `button:nth-of-type(${i + 1})`;
|
|
134
|
+
}
|
|
135
|
+
elements.buttons.push({
|
|
136
|
+
type: 'button',
|
|
137
|
+
selector,
|
|
138
|
+
text: text.trim() || ariaLabel,
|
|
139
|
+
visible: isVisible,
|
|
140
|
+
attributes: {
|
|
141
|
+
type,
|
|
142
|
+
disabled: disabled.toString(),
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// Failed to extract button info, skip
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Discover all links with href
|
|
151
|
+
const links = await page.locator('a[href]').all();
|
|
152
|
+
const seenHrefs = new Set();
|
|
153
|
+
for (const link of links) {
|
|
154
|
+
try {
|
|
155
|
+
const href = await link.getAttribute('href');
|
|
156
|
+
if (!href)
|
|
157
|
+
continue;
|
|
158
|
+
// Resolve to absolute URL
|
|
159
|
+
const absoluteUrl = new URL(href, pageUrl).href;
|
|
160
|
+
// Deduplicate by href
|
|
161
|
+
if (seenHrefs.has(absoluteUrl))
|
|
162
|
+
continue;
|
|
163
|
+
seenHrefs.add(absoluteUrl);
|
|
164
|
+
const text = (await link.textContent())?.trim() || '';
|
|
165
|
+
// Determine if internal
|
|
166
|
+
const linkHostname = new URL(absoluteUrl).hostname;
|
|
167
|
+
const isInternal = linkHostname === pageHostname;
|
|
168
|
+
elements.links.push({
|
|
169
|
+
href: absoluteUrl,
|
|
170
|
+
text,
|
|
171
|
+
isInternal,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// Failed to extract link info or invalid URL, skip
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// Discover navigation menus
|
|
179
|
+
const navContainers = await page
|
|
180
|
+
.locator('nav, [role="navigation"], [role="menubar"]')
|
|
181
|
+
.all();
|
|
182
|
+
for (let i = 0; i < navContainers.length; i++) {
|
|
183
|
+
const nav = navContainers[i];
|
|
184
|
+
try {
|
|
185
|
+
const text = (await nav.textContent())?.trim() || '';
|
|
186
|
+
const ariaLabel = (await nav.getAttribute('aria-label')) || '';
|
|
187
|
+
const isVisible = await nav.isVisible();
|
|
188
|
+
elements.menus.push({
|
|
189
|
+
type: 'menu',
|
|
190
|
+
selector: `nav:nth-of-type(${i + 1})`,
|
|
191
|
+
text: ariaLabel || text.substring(0, 50) || `Menu ${i + 1}`,
|
|
192
|
+
visible: isVisible,
|
|
193
|
+
attributes: {},
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
// Failed to extract menu info, skip
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// Discover other interactive elements
|
|
201
|
+
const interactiveRoles = ['tab', 'tabpanel', 'dialog', 'combobox', 'listbox'];
|
|
202
|
+
for (const role of interactiveRoles) {
|
|
203
|
+
const roleElements = await page.locator(`[role="${role}"]`).all();
|
|
204
|
+
for (let i = 0; i < roleElements.length; i++) {
|
|
205
|
+
const element = roleElements[i];
|
|
206
|
+
try {
|
|
207
|
+
const text = (await element.textContent())?.trim() || '';
|
|
208
|
+
const ariaLabel = (await element.getAttribute('aria-label')) || '';
|
|
209
|
+
const isVisible = await element.isVisible();
|
|
210
|
+
elements.otherInteractive.push({
|
|
211
|
+
type: role, // TypeScript will coerce to InteractiveElement['type']
|
|
212
|
+
selector: `[role="${role}"]:nth-of-type(${i + 1})`,
|
|
213
|
+
text: ariaLabel || text.substring(0, 50) || `${role} ${i + 1}`,
|
|
214
|
+
visible: isVisible,
|
|
215
|
+
attributes: { role },
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// Failed to extract element info, skip
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return elements;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Discover hidden elements by triggering interactions (buttons, menus, modals)
|
|
227
|
+
* Returns only NEWLY discovered elements (not present in initial page load)
|
|
228
|
+
*/
|
|
229
|
+
export async function discoverHiddenElements(page, pageUrl) {
|
|
230
|
+
// Take "before" snapshot of visible element count
|
|
231
|
+
const beforeCounts = {
|
|
232
|
+
buttons: (await page.getByRole('button').all()).length,
|
|
233
|
+
links: (await page.locator('a[href]').all()).length,
|
|
234
|
+
forms: (await page.locator('form').all()).length,
|
|
235
|
+
};
|
|
236
|
+
const newElements = {
|
|
237
|
+
forms: [],
|
|
238
|
+
buttons: [],
|
|
239
|
+
links: [],
|
|
240
|
+
menus: [],
|
|
241
|
+
otherInteractive: [],
|
|
242
|
+
};
|
|
243
|
+
// Find buttons that might trigger hidden content
|
|
244
|
+
const triggerSelectors = [
|
|
245
|
+
'button[aria-expanded]',
|
|
246
|
+
'button[aria-haspopup]',
|
|
247
|
+
'button[data-toggle]',
|
|
248
|
+
'button[data-bs-toggle]',
|
|
249
|
+
'[class*="hamburger"]',
|
|
250
|
+
'[class*="menu-toggle"]',
|
|
251
|
+
'[class*="navbar-toggler"]',
|
|
252
|
+
];
|
|
253
|
+
// Also find buttons with trigger-like text
|
|
254
|
+
const triggerTexts = ['menu', 'nav', 'more', 'show', 'open', 'expand'];
|
|
255
|
+
const allButtons = await page.getByRole('button').all();
|
|
256
|
+
const triggerButtons = [];
|
|
257
|
+
// Collect explicit trigger buttons
|
|
258
|
+
for (const selector of triggerSelectors) {
|
|
259
|
+
const buttons = await page.locator(selector).all();
|
|
260
|
+
triggerButtons.push(...buttons);
|
|
261
|
+
}
|
|
262
|
+
// Collect buttons with trigger-like text
|
|
263
|
+
for (const button of allButtons) {
|
|
264
|
+
try {
|
|
265
|
+
const text = ((await button.textContent()) || '').toLowerCase();
|
|
266
|
+
if (triggerTexts.some((word) => text.includes(word))) {
|
|
267
|
+
triggerButtons.push(button);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
// Skip
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Click each trigger button and check for new elements (cap at 10 to avoid perf explosion)
|
|
275
|
+
for (const trigger of triggerButtons.slice(0, 10)) {
|
|
276
|
+
try {
|
|
277
|
+
// Check if button is visible and enabled
|
|
278
|
+
const isVisible = await trigger.isVisible();
|
|
279
|
+
if (!isVisible)
|
|
280
|
+
continue;
|
|
281
|
+
// Check for destructive actions
|
|
282
|
+
const buttonText = (await trigger.textContent()) || '';
|
|
283
|
+
const ariaLabel = (await trigger.getAttribute('aria-label')) || '';
|
|
284
|
+
const combinedText = `${buttonText} ${ariaLabel}`;
|
|
285
|
+
if (isDestructiveAction(combinedText)) {
|
|
286
|
+
continue; // Skip destructive buttons
|
|
287
|
+
}
|
|
288
|
+
// Click the trigger
|
|
289
|
+
await trigger.click({ timeout: 2000 });
|
|
290
|
+
// Wait for animations
|
|
291
|
+
await page.waitForTimeout(500);
|
|
292
|
+
// Check for newly visible elements
|
|
293
|
+
const modalSelectors = [
|
|
294
|
+
'[role="dialog"]',
|
|
295
|
+
'[role="menu"]',
|
|
296
|
+
'.modal',
|
|
297
|
+
'.dropdown-menu',
|
|
298
|
+
'.nav-menu',
|
|
299
|
+
'[aria-hidden="false"]',
|
|
300
|
+
];
|
|
301
|
+
let foundNewContent = false;
|
|
302
|
+
for (const selector of modalSelectors) {
|
|
303
|
+
const modalElements = await page.locator(selector).all();
|
|
304
|
+
for (const modal of modalElements) {
|
|
305
|
+
try {
|
|
306
|
+
const isVisible = await modal.isVisible();
|
|
307
|
+
if (isVisible) {
|
|
308
|
+
foundNewContent = true;
|
|
309
|
+
// Run discoverElements on the new content
|
|
310
|
+
const modalElements = await discoverElements(page, pageUrl);
|
|
311
|
+
// Merge with newElements (deduplicate by selector)
|
|
312
|
+
for (const form of modalElements.forms) {
|
|
313
|
+
if (!newElements.forms.some((f) => f.selector === form.selector)) {
|
|
314
|
+
newElements.forms.push(form);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
for (const button of modalElements.buttons) {
|
|
318
|
+
if (!newElements.buttons.some((b) => b.selector === button.selector)) {
|
|
319
|
+
newElements.buttons.push(button);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
for (const link of modalElements.links) {
|
|
323
|
+
if (!newElements.links.some((l) => l.href === link.href)) {
|
|
324
|
+
newElements.links.push(link);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
for (const menu of modalElements.menus) {
|
|
328
|
+
if (!newElements.menus.some((m) => m.selector === menu.selector)) {
|
|
329
|
+
newElements.menus.push(menu);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
for (const other of modalElements.otherInteractive) {
|
|
333
|
+
if (!newElements.otherInteractive.some((o) => o.selector === other.selector)) {
|
|
334
|
+
newElements.otherInteractive.push(other);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
catch {
|
|
340
|
+
// Skip
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
// Try to close/reset
|
|
345
|
+
if (foundNewContent) {
|
|
346
|
+
try {
|
|
347
|
+
// Try clicking trigger again to toggle off
|
|
348
|
+
await trigger.click({ timeout: 1000 });
|
|
349
|
+
await page.waitForTimeout(300);
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
// Try pressing Escape
|
|
353
|
+
try {
|
|
354
|
+
await page.keyboard.press('Escape');
|
|
355
|
+
await page.waitForTimeout(300);
|
|
356
|
+
}
|
|
357
|
+
catch {
|
|
358
|
+
// If can't close, reload the page to reset state
|
|
359
|
+
try {
|
|
360
|
+
await page.reload({ timeout: 5000, waitUntil: 'domcontentloaded' });
|
|
361
|
+
}
|
|
362
|
+
catch {
|
|
363
|
+
// Can't reload, break loop
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// Failed to interact with trigger, continue
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// Also try hovering on nav items to discover dropdowns
|
|
376
|
+
const navItems = await page.locator('nav li, [role="menubar"] > *').all();
|
|
377
|
+
for (const item of navItems) {
|
|
378
|
+
try {
|
|
379
|
+
const isVisible = await item.isVisible();
|
|
380
|
+
if (!isVisible)
|
|
381
|
+
continue;
|
|
382
|
+
// Hover on the item
|
|
383
|
+
await item.hover({ timeout: 1000 });
|
|
384
|
+
await page.waitForTimeout(300);
|
|
385
|
+
// Check for dropdown menus
|
|
386
|
+
const dropdowns = await page.locator('.dropdown-menu, [role="menu"]').all();
|
|
387
|
+
for (const dropdown of dropdowns) {
|
|
388
|
+
try {
|
|
389
|
+
const isDropdownVisible = await dropdown.isVisible();
|
|
390
|
+
if (isDropdownVisible) {
|
|
391
|
+
// Discover elements in dropdown
|
|
392
|
+
const dropdownElements = await discoverElements(page, pageUrl);
|
|
393
|
+
// Merge new links
|
|
394
|
+
for (const link of dropdownElements.links) {
|
|
395
|
+
if (!newElements.links.some((l) => l.href === link.href)) {
|
|
396
|
+
newElements.links.push(link);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// Merge new buttons
|
|
400
|
+
for (const button of dropdownElements.buttons) {
|
|
401
|
+
if (!newElements.buttons.some((b) => b.selector === button.selector)) {
|
|
402
|
+
newElements.buttons.push(button);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
catch {
|
|
408
|
+
// Skip
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch {
|
|
413
|
+
// Failed to hover, continue
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// Filter to return only NEWLY discovered elements (subtract "before" snapshot)
|
|
418
|
+
// Since we already deduplicated during collection, and we're discovering from hidden content,
|
|
419
|
+
// most of these should be new. But let's do a final pass to be safe.
|
|
420
|
+
return newElements;
|
|
421
|
+
}
|
|
422
|
+
//# sourceMappingURL=element-mapper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"element-mapper.js","sourceRoot":"","sources":["../../src/discovery/element-mapper.ts"],"names":[],"mappings":"AAAA,0FAA0F;AAK1F,kEAAkE;AAClE,MAAM,oBAAoB,GAAG;IAC3B,QAAQ;IACR,QAAQ;IACR,SAAS;IACT,OAAO;IACP,OAAO;IACP,MAAM;IACN,OAAO;IACP,QAAQ;IACR,WAAW;IACX,aAAa;IACb,gBAAgB;IAChB,eAAe;IACf,QAAQ;IACR,SAAS;IACT,UAAU;CACX,CAAC;AAEF;;GAEG;AACH,SAAS,mBAAmB,CAAC,IAAY;IACvC,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;IAC5C,OAAO,oBAAoB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AAC3D,CAAC;AAaD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAU,EAAE,OAAe;IAChE,MAAM,QAAQ,GAAuB;QACnC,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,EAAE;QACT,KAAK,EAAE,EAAE;QACT,gBAAgB,EAAE,EAAE;KACrB,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC;IAE/C,0CAA0C;IAC1C,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC;IAC/C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAEtB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC;YACzD,MAAM,MAAM,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC;YAE5E,8BAA8B;YAC9B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;YAC7C,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YACjD,IAAI,QAAQ,GAAG,MAAM,CAAC;YACtB,IAAI,MAAM,EAAE,CAAC;gBACX,QAAQ,GAAG,QAAQ,MAAM,EAAE,CAAC;YAC9B,CAAC;iBAAM,IAAI,QAAQ,EAAE,CAAC;gBACpB,QAAQ,GAAG,cAAc,QAAQ,IAAI,CAAC;YACxC,CAAC;iBAAM,CAAC;gBACN,QAAQ,GAAG,oBAAoB,CAAC,GAAG,CAAC,GAAG,CAAC;YAC1C,CAAC;YAED,qBAAqB;YACrB,MAAM,cAAc,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;YAExE,mDAAmD;YACnD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC,GAAG,EAAE,CAAC;YACnE,MAAM,UAAU,GAAgB,EAAE,CAAC;YAEnC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,MAAM,CAAC;oBAC1D,MAAM,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;oBACtD,MAAM,QAAQ,GAAG,CAAC,MAAM,KAAK,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,KAAK,IAAI,CAAC;oBACjE,MAAM,WAAW,GAAG,CAAC,MAAM,KAAK,CAAC,YAAY,CAAC,aAAa,CAAC,CAAC,IAAI,EAAE,CAAC;oBAEpE,+BAA+B;oBAC/B,IAAI,KAAK,GAAG,CAAC,MAAM,KAAK,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;oBAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;wBACX,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;wBAC/C,IAAI,OAAO,EAAE,CAAC;4BACZ,+CAA+C;4BAC/C,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,cAAc,OAAO,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;4BAC3E,MAAM,SAAS,GAAG,MAAM,YAAY,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;4BACnE,KAAK,GAAG,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;wBAClC,CAAC;oBACH,CAAC;oBAED,sDAAsD;oBACtD,IAAI,CAAC,KAAK,EAAE,CAAC;wBACX,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,0BAA0B,CAAC,CAAC,KAAK,EAAE,CAAC;wBAC5E,MAAM,SAAS,GAAG,MAAM,WAAW,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;wBAClE,KAAK,GAAG,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;oBAClC,CAAC;oBAED,UAAU,CAAC,IAAI,CAAC;wBACd,IAAI;wBACJ,IAAI;wBACJ,KAAK;wBACL,QAAQ;wBACR,WAAW;qBACZ,CAAC,CAAC;gBACL,CAAC;gBAAC,MAAM,CAAC;oBACP,qCAAqC;gBACvC,CAAC;YACH,CAAC;YAED,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;gBAClB,MAAM,EAAE,cAAc;gBACtB,MAAM;gBACN,QAAQ;gBACR,MAAM,EAAE,UAAU;aACnB,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,oCAAoC;QACtC,CAAC;IACH,CAAC;IAED,uBAAuB;IACvB,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;IACrD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACxC,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QAE1B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;YAChD,MAAM,SAAS,GAAG,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;YAClE,MAAM,IAAI,GAAG,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,QAAQ,CAAC;YAC7D,MAAM,QAAQ,GAAG,CAAC,MAAM,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC,KAAK,IAAI,CAAC;YAClE,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,CAAC;YAE3C,uDAAuD;YACvD,IAAI,QAAQ,GAAG,QAAQ,CAAC;YACxB,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBAChB,QAAQ,GAAG,oBAAoB,kBAAkB,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC;YACrE,CAAC;iBAAM,IAAI,SAAS,EAAE,CAAC;gBACrB,QAAQ,GAAG,sBAAsB,kBAAkB,CAAC,SAAS,CAAC,IAAI,CAAC;YACrE,CAAC;iBAAM,CAAC;gBACN,0BAA0B;gBAC1B,QAAQ,GAAG,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC;YAC5C,CAAC;YAED,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC;gBACpB,IAAI,EAAE,QAAQ;gBACd,QAAQ;gBACR,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,SAAS;gBAC9B,OAAO,EAAE,SAAS;gBAClB,UAAU,EAAE;oBACV,IAAI;oBACJ,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE;iBAC9B;aACF,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,sCAAsC;QACxC,CAAC;IACH,CAAC;IAED,+BAA+B;IAC/B,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,CAAC;IAClD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;IAEpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;YAC7C,IAAI,CAAC,IAAI;gBAAE,SAAS;YAEpB,0BAA0B;YAC1B,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC;YAEhD,sBAAsB;YACtB,IAAI,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC;gBAAE,SAAS;YACzC,SAAS,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAE3B,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YAEtD,wBAAwB;YACxB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC;YACnD,MAAM,UAAU,GAAG,YAAY,KAAK,YAAY,CAAC;YAEjD,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;gBAClB,IAAI,EAAE,WAAW;gBACjB,IAAI;gBACJ,UAAU;aACX,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,mDAAmD;QACrD,CAAC;IACH,CAAC;IAED,4BAA4B;IAC5B,MAAM,aAAa,GAAG,MAAM,IAAI;SAC7B,OAAO,CAAC,4CAA4C,CAAC;SACrD,GAAG,EAAE,CAAC;IAET,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,aAAa,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,MAAM,GAAG,GAAG,aAAa,CAAC,CAAC,CAAC,CAAC;QAE7B,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;YACrD,MAAM,SAAS,GAAG,CAAC,MAAM,GAAG,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;YAC/D,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,SAAS,EAAE,CAAC;YAExC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC;gBAClB,IAAI,EAAE,MAAM;gBACZ,QAAQ,EAAE,mBAAmB,CAAC,GAAG,CAAC,GAAG;gBACrC,IAAI,EAAE,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,EAAE;gBAC3D,OAAO,EAAE,SAAS;gBAClB,UAAU,EAAE,EAAE;aACf,CAAC,CAAC;QACL,CAAC;QAAC,MAAM,CAAC;YACP,oCAAoC;QACtC,CAAC;IACH,CAAC;IAED,sCAAsC;IACtC,MAAM,gBAAgB,GAAG,CAAC,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IAE9E,KAAK,MAAM,IAAI,IAAI,gBAAgB,EAAE,CAAC;QACpC,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,GAAG,EAAE,CAAC;QAElE,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,OAAO,GAAG,YAAY,CAAC,CAAC,CAAC,CAAC;YAEhC,IAAI,CAAC;gBACH,MAAM,IAAI,GAAG,CAAC,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;gBACzD,MAAM,SAAS,GAAG,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;gBACnE,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC;gBAE5C,QAAQ,CAAC,gBAAgB,CAAC,IAAI,CAAC;oBAC7B,IAAI,EAAE,IAAW,EAAE,uDAAuD;oBAC1E,QAAQ,EAAE,UAAU,IAAI,kBAAkB,CAAC,GAAG,CAAC,GAAG;oBAClD,IAAI,EAAE,SAAS,IAAI,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE;oBAC9D,OAAO,EAAE,SAAS;oBAClB,UAAU,EAAE,EAAE,IAAI,EAAE;iBACrB,CAAC,CAAC;YACL,CAAC;YAAC,MAAM,CAAC;gBACP,uCAAuC;YACzC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,IAAU,EACV,OAAe;IAEf,kDAAkD;IAClD,MAAM,YAAY,GAAG;QACnB,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM;QACtD,KAAK,EAAE,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM;QACnD,KAAK,EAAE,CAAC,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,MAAM;KACjD,CAAC;IAEF,MAAM,WAAW,GAAuB;QACtC,KAAK,EAAE,EAAE;QACT,OAAO,EAAE,EAAE;QACX,KAAK,EAAE,EAAE;QACT,KAAK,EAAE,EAAE;QACT,gBAAgB,EAAE,EAAE;KACrB,CAAC;IAEF,iDAAiD;IACjD,MAAM,gBAAgB,GAAG;QACvB,uBAAuB;QACvB,uBAAuB;QACvB,qBAAqB;QACrB,wBAAwB;QACxB,sBAAsB;QACtB,wBAAwB;QACxB,2BAA2B;KAC5B,CAAC;IAEF,2CAA2C;IAC3C,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEvE,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;IACxD,MAAM,cAAc,GAAG,EAAE,CAAC;IAE1B,mCAAmC;IACnC,KAAK,MAAM,QAAQ,IAAI,gBAAgB,EAAE,CAAC;QACxC,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;QACnD,cAAc,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC;IAClC,CAAC;IAED,yCAAyC;IACzC,KAAK,MAAM,MAAM,IAAI,UAAU,EAAE,CAAC;QAChC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,CAAC,CAAC,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;YAChE,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC;gBACrD,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;IACH,CAAC;IAED,2FAA2F;IAC3F,KAAK,MAAM,OAAO,IAAI,cAAc,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,yCAAyC;YACzC,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,SAAS,EAAE,CAAC;YAC5C,IAAI,CAAC,SAAS;gBAAE,SAAS;YAEzB,gCAAgC;YAChC,MAAM,UAAU,GAAG,CAAC,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,CAAC;YACvD,MAAM,SAAS,GAAG,CAAC,MAAM,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;YACnE,MAAM,YAAY,GAAG,GAAG,UAAU,IAAI,SAAS,EAAE,CAAC;YAElD,IAAI,mBAAmB,CAAC,YAAY,CAAC,EAAE,CAAC;gBACtC,SAAS,CAAC,2BAA2B;YACvC,CAAC;YAED,oBAAoB;YACpB,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YAEvC,sBAAsB;YACtB,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;YAE/B,mCAAmC;YACnC,MAAM,cAAc,GAAG;gBACrB,iBAAiB;gBACjB,eAAe;gBACf,QAAQ;gBACR,gBAAgB;gBAChB,WAAW;gBACX,uBAAuB;aACxB,CAAC;YAEF,IAAI,eAAe,GAAG,KAAK,CAAC;YAE5B,KAAK,MAAM,QAAQ,IAAI,cAAc,EAAE,CAAC;gBACtC,MAAM,aAAa,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;gBAEzD,KAAK,MAAM,KAAK,IAAI,aAAa,EAAE,CAAC;oBAClC,IAAI,CAAC;wBACH,MAAM,SAAS,GAAG,MAAM,KAAK,CAAC,SAAS,EAAE,CAAC;wBAC1C,IAAI,SAAS,EAAE,CAAC;4BACd,eAAe,GAAG,IAAI,CAAC;4BAEvB,0CAA0C;4BAC1C,MAAM,aAAa,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;4BAE5D,mDAAmD;4BACnD,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC;gCACvC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;oCACjE,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gCAC/B,CAAC;4BACH,CAAC;4BAED,KAAK,MAAM,MAAM,IAAI,aAAa,CAAC,OAAO,EAAE,CAAC;gCAC3C,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;oCACrE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;gCACnC,CAAC;4BACH,CAAC;4BAED,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC;gCACvC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;oCACzD,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gCAC/B,CAAC;4BACH,CAAC;4BAED,KAAK,MAAM,IAAI,IAAI,aAAa,CAAC,KAAK,EAAE,CAAC;gCACvC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;oCACjE,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gCAC/B,CAAC;4BACH,CAAC;4BAED,KAAK,MAAM,KAAK,IAAI,aAAa,CAAC,gBAAgB,EAAE,CAAC;gCACnD,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC;oCAC7E,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gCAC3C,CAAC;4BACH,CAAC;wBACH,CAAC;oBACH,CAAC;oBAAC,MAAM,CAAC;wBACP,OAAO;oBACT,CAAC;gBACH,CAAC;YACH,CAAC;YAED,qBAAqB;YACrB,IAAI,eAAe,EAAE,CAAC;gBACpB,IAAI,CAAC;oBACH,2CAA2C;oBAC3C,MAAM,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;oBACvC,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;gBACjC,CAAC;gBAAC,MAAM,CAAC;oBACP,sBAAsB;oBACtB,IAAI,CAAC;wBACH,MAAM,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;wBACpC,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;oBACjC,CAAC;oBAAC,MAAM,CAAC;wBACP,iDAAiD;wBACjD,IAAI,CAAC;4BACH,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,kBAAkB,EAAE,CAAC,CAAC;wBACtE,CAAC;wBAAC,MAAM,CAAC;4BACP,2BAA2B;4BAC3B,MAAM;wBACR,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,4CAA4C;YAC5C,SAAS;QACX,CAAC;IACH,CAAC;IAED,uDAAuD;IACvD,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,8BAA8B,CAAC,CAAC,GAAG,EAAE,CAAC;IAE1E,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;QAC5B,IAAI,CAAC;YACH,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;YACzC,IAAI,CAAC,SAAS;gBAAE,SAAS;YAEzB,oBAAoB;YACpB,MAAM,IAAI,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;YACpC,MAAM,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;YAE/B,2BAA2B;YAC3B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC,GAAG,EAAE,CAAC;YAE5E,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;gBACjC,IAAI,CAAC;oBACH,MAAM,iBAAiB,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,CAAC;oBACrD,IAAI,iBAAiB,EAAE,CAAC;wBACtB,gCAAgC;wBAChC,MAAM,gBAAgB,GAAG,MAAM,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;wBAE/D,kBAAkB;wBAClB,KAAK,MAAM,IAAI,IAAI,gBAAgB,CAAC,KAAK,EAAE,CAAC;4BAC1C,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gCACzD,WAAW,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;4BAC/B,CAAC;wBACH,CAAC;wBAED,oBAAoB;wBACpB,KAAK,MAAM,MAAM,IAAI,gBAAgB,CAAC,OAAO,EAAE,CAAC;4BAC9C,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;gCACrE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;4BACnC,CAAC;wBACH,CAAC;oBACH,CAAC;gBACH,CAAC;gBAAC,MAAM,CAAC;oBACP,OAAO;gBACT,CAAC;YACH,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,4BAA4B;YAC5B,SAAS;QACX,CAAC;IACH,CAAC;IAED,+EAA+E;IAC/E,8FAA8F;IAC9F,qEAAqE;IAErE,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { SiteCrawler } from './crawler.js';
|
|
2
|
+
export type { CrawlerOptions } from './crawler.js';
|
|
3
|
+
export { detectSPAFramework, interceptRouteChanges } from './spa-detector.js';
|
|
4
|
+
export { discoverElements, discoverHiddenElements } from './element-mapper.js';
|
|
5
|
+
export { validateLinks, createLinkValidationState } from './link-validator.js';
|
|
6
|
+
export { buildSitemap, printSitemapTree } from './sitemap-builder.js';
|
|
7
|
+
export { runDiscovery } from './discovery-pipeline.js';
|
|
8
|
+
export type { DiscoveryOptions } from './discovery-pipeline.js';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Barrel export for discovery module
|
|
2
|
+
export { SiteCrawler } from './crawler.js';
|
|
3
|
+
export { detectSPAFramework, interceptRouteChanges } from './spa-detector.js';
|
|
4
|
+
export { discoverElements, discoverHiddenElements } from './element-mapper.js';
|
|
5
|
+
export { validateLinks, createLinkValidationState } from './link-validator.js';
|
|
6
|
+
export { buildSitemap, printSitemapTree } from './sitemap-builder.js';
|
|
7
|
+
export { runDiscovery } from './discovery-pipeline.js';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/discovery/index.ts"],"names":[],"mappings":"AAAA,qCAAqC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,aAAa,EAAE,yBAAyB,EAAE,MAAM,qBAAqB,CAAC;AAC/E,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Page } from 'playwright';
|
|
2
|
+
import type { BrokenLink, LinkInfo } from '../types/discovery.js';
|
|
3
|
+
export interface LinkValidationState {
|
|
4
|
+
checkedCount: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function createLinkValidationState(): LinkValidationState;
|
|
7
|
+
/**
|
|
8
|
+
* Validates internal links by checking their HTTP status codes.
|
|
9
|
+
* External links are skipped to avoid rate-limiting third-party sites.
|
|
10
|
+
*
|
|
11
|
+
* @param links - All links discovered on a page
|
|
12
|
+
* @param page - Playwright Page instance (maintains session context for auth)
|
|
13
|
+
* @returns Array of broken links (4xx, 5xx, network errors)
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateLinks(links: LinkInfo[], page: Page, state?: LinkValidationState): Promise<BrokenLink[]>;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { ensurePublicHostname } from '../core/validation.js';
|
|
2
|
+
// Security caps for link validation
|
|
3
|
+
const MAX_LINKS_PER_PAGE = 50;
|
|
4
|
+
const MAX_TOTAL_LINKS = 500;
|
|
5
|
+
export function createLinkValidationState() {
|
|
6
|
+
return { checkedCount: 0 };
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Validates internal links by checking their HTTP status codes.
|
|
10
|
+
* External links are skipped to avoid rate-limiting third-party sites.
|
|
11
|
+
*
|
|
12
|
+
* @param links - All links discovered on a page
|
|
13
|
+
* @param page - Playwright Page instance (maintains session context for auth)
|
|
14
|
+
* @returns Array of broken links (4xx, 5xx, network errors)
|
|
15
|
+
*/
|
|
16
|
+
export async function validateLinks(links, page, state = createLinkValidationState()) {
|
|
17
|
+
const brokenLinks = [];
|
|
18
|
+
const sourceUrl = page.url();
|
|
19
|
+
const hostnameSafetyCache = new Map();
|
|
20
|
+
const assertHostnameIsPublic = async (hostname) => {
|
|
21
|
+
const normalized = hostname.toLowerCase();
|
|
22
|
+
if (hostnameSafetyCache.has(normalized)) {
|
|
23
|
+
const cachedError = hostnameSafetyCache.get(normalized);
|
|
24
|
+
if (cachedError) {
|
|
25
|
+
throw new Error(cachedError);
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
await ensurePublicHostname(normalized);
|
|
31
|
+
hostnameSafetyCache.set(normalized, null);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
35
|
+
hostnameSafetyCache.set(normalized, message);
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
// Filter to internal links only
|
|
40
|
+
const internalLinks = links.filter(link => link.isInternal);
|
|
41
|
+
// Deduplicate by href (normalize query strings)
|
|
42
|
+
const uniqueUrls = new Map();
|
|
43
|
+
for (const link of internalLinks) {
|
|
44
|
+
try {
|
|
45
|
+
const url = new URL(link.href);
|
|
46
|
+
// Normalize: remove trailing slash, lowercase hostname
|
|
47
|
+
const normalizedHref = `${url.origin}${url.pathname.replace(/\/$/, '')}${url.search}`;
|
|
48
|
+
if (!uniqueUrls.has(normalizedHref)) {
|
|
49
|
+
uniqueUrls.set(normalizedHref, { ...link, href: normalizedHref });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// Invalid URL, skip
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Filter out non-HTTP links
|
|
58
|
+
const httpLinks = Array.from(uniqueUrls.values()).filter(link => {
|
|
59
|
+
const href = link.href.toLowerCase();
|
|
60
|
+
return !href.startsWith('mailto:') &&
|
|
61
|
+
!href.startsWith('tel:') &&
|
|
62
|
+
!href.startsWith('javascript:') &&
|
|
63
|
+
!href.startsWith('data:') &&
|
|
64
|
+
!href.startsWith('#');
|
|
65
|
+
});
|
|
66
|
+
// Apply per-page cap
|
|
67
|
+
const cappedLinks = httpLinks.slice(0, MAX_LINKS_PER_PAGE);
|
|
68
|
+
if (httpLinks.length > MAX_LINKS_PER_PAGE) {
|
|
69
|
+
console.warn(`Capping link validation to ${MAX_LINKS_PER_PAGE} links per page (${httpLinks.length} found)`);
|
|
70
|
+
}
|
|
71
|
+
// Apply global cap
|
|
72
|
+
const remainingGlobalCapacity = MAX_TOTAL_LINKS - state.checkedCount;
|
|
73
|
+
const linksToCheck = cappedLinks.slice(0, remainingGlobalCapacity);
|
|
74
|
+
if (linksToCheck.length < cappedLinks.length) {
|
|
75
|
+
console.warn(`Global link cap reached (${MAX_TOTAL_LINKS} total). Skipping ${cappedLinks.length - linksToCheck.length} links.`);
|
|
76
|
+
}
|
|
77
|
+
state.checkedCount += linksToCheck.length;
|
|
78
|
+
// Check links with concurrency limit (max 10 concurrent)
|
|
79
|
+
const batchSize = 10;
|
|
80
|
+
for (let i = 0; i < linksToCheck.length; i += batchSize) {
|
|
81
|
+
const batch = linksToCheck.slice(i, i + batchSize);
|
|
82
|
+
const results = await Promise.allSettled(batch.map(async (link) => {
|
|
83
|
+
try {
|
|
84
|
+
const initialParsedUrl = new URL(link.href);
|
|
85
|
+
await assertHostnameIsPublic(initialParsedUrl.hostname);
|
|
86
|
+
// Use page.request to maintain browser session context (cookies, auth)
|
|
87
|
+
const response = await page.request.get(link.href, {
|
|
88
|
+
timeout: 5000,
|
|
89
|
+
// Follow redirects by default - only final status matters
|
|
90
|
+
});
|
|
91
|
+
// SSRF protection: check final redirect destination
|
|
92
|
+
const finalUrl = response.url();
|
|
93
|
+
if (finalUrl !== link.href) {
|
|
94
|
+
try {
|
|
95
|
+
const finalParsed = new URL(finalUrl);
|
|
96
|
+
await assertHostnameIsPublic(finalParsed.hostname);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
return {
|
|
100
|
+
url: link.href,
|
|
101
|
+
sourceUrl,
|
|
102
|
+
statusCode: 0,
|
|
103
|
+
statusText: error instanceof Error ? error.message : 'SSRF protection: Redirect destination failed validation',
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Check if response is broken (4xx, 5xx)
|
|
108
|
+
if (response.status() >= 400) {
|
|
109
|
+
return {
|
|
110
|
+
url: link.href,
|
|
111
|
+
sourceUrl,
|
|
112
|
+
statusCode: response.status(),
|
|
113
|
+
statusText: response.statusText(),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
return null; // Link is OK
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
// Network error or timeout
|
|
120
|
+
return {
|
|
121
|
+
url: link.href,
|
|
122
|
+
sourceUrl,
|
|
123
|
+
statusCode: 0,
|
|
124
|
+
statusText: error instanceof Error ? error.message : 'Network error',
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}));
|
|
128
|
+
// Collect broken links from this batch
|
|
129
|
+
for (const result of results) {
|
|
130
|
+
if (result.status === 'fulfilled' && result.value !== null) {
|
|
131
|
+
brokenLinks.push(result.value);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return brokenLinks;
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=link-validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-validator.js","sourceRoot":"","sources":["../../src/discovery/link-validator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,oCAAoC;AACpC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAC9B,MAAM,eAAe,GAAG,GAAG,CAAC;AAM5B,MAAM,UAAU,yBAAyB;IACvC,OAAO,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;AAC7B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAiB,EACjB,IAAU,EACV,QAA6B,yBAAyB,EAAE;IAExD,MAAM,WAAW,GAAiB,EAAE,CAAC;IACrC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,mBAAmB,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE7D,MAAM,sBAAsB,GAAG,KAAK,EAAE,QAAgB,EAAiB,EAAE;QACvE,MAAM,UAAU,GAAG,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC1C,IAAI,mBAAmB,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACxC,MAAM,WAAW,GAAG,mBAAmB,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;YACxD,IAAI,WAAW,EAAE,CAAC;gBAChB,MAAM,IAAI,KAAK,CAAC,WAAW,CAAC,CAAC;YAC/B,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,MAAM,oBAAoB,CAAC,UAAU,CAAC,CAAC;YACvC,mBAAmB,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,mBAAmB,CAAC,GAAG,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAC7C,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC,CAAC;IAEF,gCAAgC;IAChC,MAAM,aAAa,GAAG,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAE5D,gDAAgD;IAChD,MAAM,UAAU,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC/C,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC/B,uDAAuD;YACvD,MAAM,cAAc,GAAG,GAAG,GAAG,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,EAAE,CAAC;YAEtF,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,cAAc,CAAC,EAAE,CAAC;gBACpC,UAAU,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,GAAG,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAC;YACpE,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,oBAAoB;YACpB,SAAS;QACX,CAAC;IACH,CAAC;IAED,4BAA4B;IAC5B,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE;QAC9D,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;YAC3B,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;YACxB,CAAC,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;YAC/B,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;YACzB,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;IAEH,qBAAqB;IACrB,MAAM,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,kBAAkB,CAAC,CAAC;IAC3D,IAAI,SAAS,CAAC,MAAM,GAAG,kBAAkB,EAAE,CAAC;QAC1C,OAAO,CAAC,IAAI,CAAC,8BAA8B,kBAAkB,oBAAoB,SAAS,CAAC,MAAM,SAAS,CAAC,CAAC;IAC9G,CAAC;IAED,mBAAmB;IACnB,MAAM,uBAAuB,GAAG,eAAe,GAAG,KAAK,CAAC,YAAY,CAAC;IACrE,MAAM,YAAY,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,uBAAuB,CAAC,CAAC;IAEnE,IAAI,YAAY,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,4BAA4B,eAAe,qBAAqB,WAAW,CAAC,MAAM,GAAG,YAAY,CAAC,MAAM,SAAS,CAAC,CAAC;IAClI,CAAC;IAED,KAAK,CAAC,YAAY,IAAI,YAAY,CAAC,MAAM,CAAC;IAE1C,yDAAyD;IACzD,MAAM,SAAS,GAAG,EAAE,CAAC;IACrB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,IAAI,SAAS,EAAE,CAAC;QACxD,MAAM,KAAK,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC;QAEnD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,UAAU,CACtC,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YACvB,IAAI,CAAC;gBACH,MAAM,gBAAgB,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5C,MAAM,sBAAsB,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;gBAExD,uEAAuE;gBACvE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE;oBACjD,OAAO,EAAE,IAAI;oBACb,0DAA0D;iBAC3D,CAAC,CAAC;gBAEH,oDAAoD;gBACpD,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,EAAE,CAAC;gBAChC,IAAI,QAAQ,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;oBAC3B,IAAI,CAAC;wBACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;wBACtC,MAAM,sBAAsB,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;oBACrD,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,OAAO;4BACL,GAAG,EAAE,IAAI,CAAC,IAAI;4BACd,SAAS;4BACT,UAAU,EAAE,CAAC;4BACb,UAAU,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,yDAAyD;yBAC/G,CAAC;oBACJ,CAAC;gBACH,CAAC;gBAED,yCAAyC;gBACzC,IAAI,QAAQ,CAAC,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;oBAC7B,OAAO;wBACL,GAAG,EAAE,IAAI,CAAC,IAAI;wBACd,SAAS;wBACT,UAAU,EAAE,QAAQ,CAAC,MAAM,EAAE;wBAC7B,UAAU,EAAE,QAAQ,CAAC,UAAU,EAAE;qBAClC,CAAC;gBACJ,CAAC;gBAED,OAAO,IAAI,CAAC,CAAC,aAAa;YAC5B,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,2BAA2B;gBAC3B,OAAO;oBACL,GAAG,EAAE,IAAI,CAAC,IAAI;oBACd,SAAS;oBACT,UAAU,EAAE,CAAC;oBACb,UAAU,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe;iBACrE,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CACH,CAAC;QAEF,uCAAuC;QACvC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,MAAM,CAAC,MAAM,KAAK,WAAW,IAAI,MAAM,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBAC3D,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,WAAW,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SitemapNode, PageData } from '../types/discovery.js';
|
|
2
|
+
/**
|
|
3
|
+
* Builds a hierarchical sitemap tree from flat page data based on URL path structure.
|
|
4
|
+
* Creates intermediate placeholder nodes for uncrawled parent paths.
|
|
5
|
+
*
|
|
6
|
+
* @param pages - Flat array of crawled pages
|
|
7
|
+
* @param rootUrl - Root URL of the site (e.g., https://example.com)
|
|
8
|
+
* @returns Root node of the sitemap tree
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildSitemap(pages: PageData[], rootUrl: string): SitemapNode;
|
|
11
|
+
/**
|
|
12
|
+
* Renders a sitemap tree as human-readable indented text.
|
|
13
|
+
* Format: Title (path) with 2 spaces per depth level.
|
|
14
|
+
*
|
|
15
|
+
* @param node - Root or any node in the tree
|
|
16
|
+
* @param indent - Current indentation level (internal use)
|
|
17
|
+
* @returns Multi-line string representation of the tree
|
|
18
|
+
*/
|
|
19
|
+
export declare function printSitemapTree(node: SitemapNode, indent?: number): string;
|