@qulib/core 0.4.2 → 0.4.3
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 +34 -8
- package/dist/analyze.d.ts.map +1 -1
- package/dist/analyze.js +84 -5
- package/dist/cli/auth-login-run.d.ts.map +1 -1
- package/dist/cli/auth-login-run.js +26 -2
- package/dist/cli/index.js +9 -6
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -5
- package/dist/phases/observe.js +2 -2
- package/dist/phases/think.js +1 -1
- package/dist/telemetry/telemetry.interface.d.ts +1 -1
- package/dist/telemetry/telemetry.interface.d.ts.map +1 -1
- package/dist/tools/apply-auth.d.ts +4 -0
- package/dist/tools/apply-auth.d.ts.map +1 -0
- package/dist/tools/apply-auth.js +35 -0
- package/dist/tools/auth/apply.d.ts +4 -0
- package/dist/tools/auth/apply.d.ts.map +1 -0
- package/dist/tools/auth/apply.js +35 -0
- package/dist/tools/auth/block-gap.d.ts +9 -0
- package/dist/tools/auth/block-gap.d.ts.map +1 -0
- package/dist/tools/auth/block-gap.js +52 -0
- package/dist/tools/auth/custom-providers.d.ts +15 -0
- package/dist/tools/auth/custom-providers.d.ts.map +1 -0
- package/dist/tools/auth/custom-providers.js +62 -0
- package/dist/tools/auth/detect.d.ts +23 -0
- package/dist/tools/auth/detect.d.ts.map +1 -0
- package/dist/tools/auth/detect.js +526 -0
- package/dist/tools/auth/detector.d.ts +23 -0
- package/dist/tools/auth/detector.d.ts.map +1 -0
- package/dist/tools/auth/detector.js +526 -0
- package/dist/tools/auth/explore.d.ts +4 -0
- package/dist/tools/auth/explore.d.ts.map +1 -0
- package/dist/tools/auth/explore.js +346 -0
- package/dist/tools/auth/explorer.d.ts +4 -0
- package/dist/tools/auth/explorer.d.ts.map +1 -0
- package/dist/tools/auth/explorer.js +346 -0
- package/dist/tools/auth/gaps.d.ts +9 -0
- package/dist/tools/auth/gaps.d.ts.map +1 -0
- package/dist/tools/auth/gaps.js +52 -0
- package/dist/tools/auth/oauth-providers.d.ts +7 -0
- package/dist/tools/auth/oauth-providers.d.ts.map +1 -0
- package/dist/tools/auth/oauth-providers.js +21 -0
- package/dist/tools/auth/providers.d.ts +7 -0
- package/dist/tools/auth/providers.d.ts.map +1 -0
- package/dist/tools/auth/providers.js +21 -0
- package/dist/tools/auth/surface-analyzer.d.ts +4 -0
- package/dist/tools/auth/surface-analyzer.d.ts.map +1 -0
- package/dist/tools/auth/surface-analyzer.js +170 -0
- package/dist/tools/auth/surface.d.ts +4 -0
- package/dist/tools/auth/surface.d.ts.map +1 -0
- package/dist/tools/auth/surface.js +170 -0
- package/dist/tools/auth/user-providers.d.ts +15 -0
- package/dist/tools/auth/user-providers.d.ts.map +1 -0
- package/dist/tools/auth/user-providers.js +62 -0
- package/dist/tools/auth-block-gap.d.ts +6 -0
- package/dist/tools/auth-block-gap.d.ts.map +1 -1
- package/dist/tools/auth-block-gap.js +42 -9
- package/dist/tools/auth-detector.d.ts +9 -8
- package/dist/tools/auth-detector.d.ts.map +1 -1
- package/dist/tools/auth-detector.js +106 -8
- package/dist/tools/explorers/browser.d.ts +3 -0
- package/dist/tools/explorers/browser.d.ts.map +1 -0
- package/dist/tools/explorers/browser.js +13 -0
- package/dist/tools/explorers/cypress-explorer.d.ts +8 -0
- package/dist/tools/explorers/cypress-explorer.d.ts.map +1 -0
- package/dist/tools/explorers/cypress-explorer.js +5 -0
- package/dist/tools/explorers/cypress.d.ts +8 -0
- package/dist/tools/explorers/cypress.d.ts.map +1 -0
- package/dist/tools/explorers/cypress.js +5 -0
- package/dist/tools/explorers/explorer.interface.d.ts +7 -0
- package/dist/tools/explorers/explorer.interface.d.ts.map +1 -0
- package/dist/tools/explorers/explorer.interface.js +1 -0
- package/dist/tools/explorers/factory.d.ts +4 -0
- package/dist/tools/explorers/factory.d.ts.map +1 -0
- package/dist/tools/explorers/factory.js +12 -0
- package/dist/tools/explorers/playwright-explorer.d.ts +8 -0
- package/dist/tools/explorers/playwright-explorer.d.ts.map +1 -0
- package/dist/tools/explorers/playwright-explorer.js +172 -0
- package/dist/tools/explorers/playwright.d.ts +8 -0
- package/dist/tools/explorers/playwright.d.ts.map +1 -0
- package/dist/tools/explorers/playwright.js +172 -0
- package/dist/tools/explorers/types.d.ts +7 -0
- package/dist/tools/explorers/types.d.ts.map +1 -0
- package/dist/tools/explorers/types.js +1 -0
- package/dist/tools/playwright-explorer.js +1 -1
- package/dist/tools/repo/detect-framework.d.ts +15 -0
- package/dist/tools/repo/detect-framework.d.ts.map +1 -0
- package/dist/tools/repo/detect-framework.js +153 -0
- package/dist/tools/repo/framework-detector.d.ts +15 -0
- package/dist/tools/repo/framework-detector.d.ts.map +1 -0
- package/dist/tools/repo/framework-detector.js +153 -0
- package/dist/tools/repo/scan.d.ts +19 -0
- package/dist/tools/repo/scan.d.ts.map +1 -0
- package/dist/tools/repo/scan.js +181 -0
- package/dist/tools/repo/scanner.d.ts +19 -0
- package/dist/tools/repo/scanner.d.ts.map +1 -0
- package/dist/tools/repo/scanner.js +181 -0
- package/dist/tools/scoring/automation-maturity.d.ts +4 -0
- package/dist/tools/scoring/automation-maturity.d.ts.map +1 -0
- package/dist/tools/scoring/automation-maturity.js +219 -0
- package/dist/tools/scoring/gap-engine.d.ts +8 -0
- package/dist/tools/scoring/gap-engine.d.ts.map +1 -0
- package/dist/tools/scoring/gap-engine.js +138 -0
- package/dist/tools/scoring/gaps.d.ts +8 -0
- package/dist/tools/scoring/gaps.d.ts.map +1 -0
- package/dist/tools/scoring/gaps.js +138 -0
- package/dist/tools/scoring/public-surface.d.ts +5 -0
- package/dist/tools/scoring/public-surface.d.ts.map +1 -0
- package/dist/tools/scoring/public-surface.js +13 -0
- package/package.json +3 -3
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DetectedAuth } from '../../schemas/config.schema.js';
|
|
2
|
+
import type { Gap } from '../../schemas/gap-analysis.schema.js';
|
|
3
|
+
export declare function analyzeAuthSurfaceGaps(url: string, detection: DetectedAuth, timeoutMs: number): Promise<Gap[]>;
|
|
4
|
+
//# sourceMappingURL=surface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"surface.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/surface.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,sCAAsC,CAAC;AAWhE,wBAAsB,sBAAsB,CAC1C,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,YAAY,EACvB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,GAAG,EAAE,CAAC,CAwKhB"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { launchBrowser } from '../explorers/browser.js';
|
|
3
|
+
async function waitNetworkIdleBestEffort(page) {
|
|
4
|
+
try {
|
|
5
|
+
await page.waitForLoadState('networkidle', { timeout: 5000 });
|
|
6
|
+
}
|
|
7
|
+
catch {
|
|
8
|
+
/* best-effort */
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export async function analyzeAuthSurfaceGaps(url, detection, timeoutMs) {
|
|
12
|
+
if (!detection.hasAuth) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
const gaps = [];
|
|
16
|
+
const browser = await launchBrowser();
|
|
17
|
+
try {
|
|
18
|
+
const context = await browser.newContext();
|
|
19
|
+
const page = await context.newPage();
|
|
20
|
+
const resp = await page.goto(url, { timeout: timeoutMs, waitUntil: 'domcontentloaded' }).catch(() => null);
|
|
21
|
+
if (!resp || !resp.ok()) {
|
|
22
|
+
gaps.push({
|
|
23
|
+
id: randomUUID(),
|
|
24
|
+
path: new URL(url).pathname || '/',
|
|
25
|
+
severity: 'critical',
|
|
26
|
+
category: 'auth-surface',
|
|
27
|
+
reason: 'Sign-in surface did not load successfully for evaluation.',
|
|
28
|
+
description: 'The auth entry URL failed to load or returned a non-OK status before DOM checks could run.',
|
|
29
|
+
recommendation: 'Verify DNS, TLS, and that the URL is reachable from the scan environment.',
|
|
30
|
+
});
|
|
31
|
+
return gaps;
|
|
32
|
+
}
|
|
33
|
+
await waitNetworkIdleBestEffort(page);
|
|
34
|
+
const title = await page.title().catch(() => '');
|
|
35
|
+
if (!title || title.trim().length < 3) {
|
|
36
|
+
gaps.push({
|
|
37
|
+
id: randomUUID(),
|
|
38
|
+
path: '/',
|
|
39
|
+
severity: 'medium',
|
|
40
|
+
category: 'auth-surface',
|
|
41
|
+
reason: 'Missing or trivial document title on the sign-in surface.',
|
|
42
|
+
description: 'Users and assistive tech rely on a meaningful window title.',
|
|
43
|
+
recommendation: 'Set a concise, unique <title> for the login experience.',
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const metaDesc = await page.locator('meta[name="description"]').getAttribute('content').catch(() => null);
|
|
47
|
+
if (!metaDesc || metaDesc.trim().length < 8) {
|
|
48
|
+
gaps.push({
|
|
49
|
+
id: randomUUID(),
|
|
50
|
+
path: '/',
|
|
51
|
+
severity: 'low',
|
|
52
|
+
category: 'auth-surface',
|
|
53
|
+
reason: 'No meta description on the sign-in surface.',
|
|
54
|
+
description: 'Search and sharing previews benefit from meta description on public entry pages.',
|
|
55
|
+
recommendation: 'Add <meta name="description" content="..."> with a short summary of the product.',
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
const h1Count = await page.locator('h1').count();
|
|
59
|
+
if (h1Count === 0) {
|
|
60
|
+
gaps.push({
|
|
61
|
+
id: randomUUID(),
|
|
62
|
+
path: '/',
|
|
63
|
+
severity: 'medium',
|
|
64
|
+
category: 'auth-surface',
|
|
65
|
+
reason: 'No visible primary heading (h1) on the sign-in surface.',
|
|
66
|
+
description: 'A primary heading helps users orient on the login page.',
|
|
67
|
+
recommendation: 'Add a single descriptive <h1> for the sign-in view.',
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
const oauthButtons = page.locator('button, a[href], [role="button"]');
|
|
71
|
+
const n = await oauthButtons.count();
|
|
72
|
+
for (let i = 0; i < Math.min(n, 25); i++) {
|
|
73
|
+
const el = oauthButtons.nth(i);
|
|
74
|
+
const text = ((await el.textContent()) ?? '').trim();
|
|
75
|
+
if (!text || text.length > 120)
|
|
76
|
+
continue;
|
|
77
|
+
const isOAuthish = /google|microsoft|github|apple|sso|sign in with|log in with|continue with|oauth/i.test(text);
|
|
78
|
+
if (!isOAuthish)
|
|
79
|
+
continue;
|
|
80
|
+
const role = await el.getAttribute('role');
|
|
81
|
+
const tag = await el.evaluate((node) => node.tagName.toLowerCase());
|
|
82
|
+
const tabIndex = await el.getAttribute('tabindex');
|
|
83
|
+
const aria = await el.getAttribute('aria-label');
|
|
84
|
+
const keyboardable = tag === 'button' || tag === 'a' || role === 'button';
|
|
85
|
+
const labeled = Boolean(aria && aria.trim().length > 0) || text.length > 0;
|
|
86
|
+
if (!keyboardable || tabIndex === '-1') {
|
|
87
|
+
gaps.push({
|
|
88
|
+
id: randomUUID(),
|
|
89
|
+
path: '/',
|
|
90
|
+
severity: 'high',
|
|
91
|
+
category: 'auth-surface',
|
|
92
|
+
reason: `OAuth control "${text.slice(0, 60)}" may not be keyboard-accessible.`,
|
|
93
|
+
description: 'SSO entry points should be real buttons or links with focus support.',
|
|
94
|
+
recommendation: 'Use <button> or <a href> with visible label; avoid tabindex=-1 on the only sign-in path.',
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
else if (!labeled) {
|
|
98
|
+
gaps.push({
|
|
99
|
+
id: randomUUID(),
|
|
100
|
+
path: '/',
|
|
101
|
+
severity: 'medium',
|
|
102
|
+
category: 'auth-surface',
|
|
103
|
+
reason: `OAuth control "${text.slice(0, 60)}" lacks aria-label and has weak visible text.`,
|
|
104
|
+
description: 'Assistive technologies need a clear accessible name for IdP buttons.',
|
|
105
|
+
recommendation: 'Add aria-label or visible text that names the provider and action.',
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const hasPassword = (await page.locator('input[type="password"]').count()) > 0;
|
|
110
|
+
const hasEmailLink = await page.getByText(/magic link|email.*link|passwordless/i).count();
|
|
111
|
+
const hasOAuthUi = detection.oauthButtons.length > 0 ||
|
|
112
|
+
(await page.getByText(/sign in with|continue with google|microsoft|github/i).count()) > 0;
|
|
113
|
+
const formLoginFallbacks = (detection.authOptions ?? []).filter((o) => o.type === 'form-login');
|
|
114
|
+
const hasFormLoginFallback = formLoginFallbacks.length > 0;
|
|
115
|
+
if (detection.type === 'oauth' && hasOAuthUi && !hasPassword && !hasEmailLink) {
|
|
116
|
+
if (hasFormLoginFallback) {
|
|
117
|
+
const labels = formLoginFallbacks.map((o) => o.label).join(', ');
|
|
118
|
+
gaps.push({
|
|
119
|
+
id: randomUUID(),
|
|
120
|
+
path: '/',
|
|
121
|
+
severity: 'low',
|
|
122
|
+
category: 'auth-surface',
|
|
123
|
+
reason: `OAuth-primary login with form-login fallback detected via: ${labels}`,
|
|
124
|
+
description: 'A form-based login path exists alongside OAuth. Automate via type="form-login" using the selectors in authOptions.',
|
|
125
|
+
recommendation: `Automatable form option(s): ${labels}. Configure type="form-login" with credentials and selectors from detectedAuth.authOptions.`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
gaps.push({
|
|
130
|
+
id: randomUUID(),
|
|
131
|
+
path: '/',
|
|
132
|
+
severity: 'medium',
|
|
133
|
+
category: 'auth-surface',
|
|
134
|
+
reason: 'OAuth-only entry with no visible password or magic-link fallback.',
|
|
135
|
+
description: 'Users who cannot use a social IdP need another path (email/password, help, or support).',
|
|
136
|
+
recommendation: 'Add a documented fallback (email/password, help desk link, or alternate IdP).',
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
const errorSelectors = '[role="alert"], [data-testid*="error" i], .error, .alert-danger, [class*="error" i]';
|
|
141
|
+
const errCount = await page.locator(errorSelectors).count();
|
|
142
|
+
if (errCount === 0 && hasOAuthUi) {
|
|
143
|
+
gaps.push({
|
|
144
|
+
id: randomUUID(),
|
|
145
|
+
path: '/',
|
|
146
|
+
severity: 'low',
|
|
147
|
+
category: 'auth-surface',
|
|
148
|
+
reason: 'No obvious in-DOM error container found for OAuth sign-in failures.',
|
|
149
|
+
description: 'IdP failures should surface recoverable feedback in the page.',
|
|
150
|
+
recommendation: 'Reserve a live region or inline alert for OAuth errors returned from the provider.',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const help = await page.getByText(/forgot password|need help|contact support|get help/i).count();
|
|
154
|
+
if (help === 0 && hasOAuthUi) {
|
|
155
|
+
gaps.push({
|
|
156
|
+
id: randomUUID(),
|
|
157
|
+
path: '/',
|
|
158
|
+
severity: 'low',
|
|
159
|
+
category: 'auth-surface',
|
|
160
|
+
reason: 'No visible “forgot password” or help path detected near OAuth controls.',
|
|
161
|
+
description: 'Users locked out of an IdP need a support or recovery affordance.',
|
|
162
|
+
recommendation: 'Link to account recovery, IT help, or a support URL near the sign-in actions.',
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
await browser.close();
|
|
168
|
+
}
|
|
169
|
+
return gaps;
|
|
170
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { OAuthProvider } from './oauth-providers.js';
|
|
2
|
+
export interface SerializedProvider {
|
|
3
|
+
id: string;
|
|
4
|
+
label: string;
|
|
5
|
+
patterns: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function loadUserProviders(): OAuthProvider[];
|
|
8
|
+
export declare function addUserProvider(input: {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
pattern: string;
|
|
12
|
+
}): void;
|
|
13
|
+
export declare function removeUserProvider(id: string): boolean;
|
|
14
|
+
export declare function listUserProviders(): SerializedProvider[];
|
|
15
|
+
//# sourceMappingURL=user-providers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"user-providers.d.ts","sourceRoot":"","sources":["../../../src/tools/auth/user-providers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAI1D,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,wBAAgB,iBAAiB,IAAI,aAAa,EAAE,CAOnD;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAc3F;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAStD;AAED,wBAAgB,iBAAiB,IAAI,kBAAkB,EAAE,CAExD"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
const USER_PROVIDERS_PATH = join(homedir(), '.qulib', 'providers.json');
|
|
5
|
+
export function loadUserProviders() {
|
|
6
|
+
const raw = loadSerialized();
|
|
7
|
+
return raw.map((p) => ({
|
|
8
|
+
id: p.id,
|
|
9
|
+
label: p.label,
|
|
10
|
+
patterns: p.patterns.map((src) => new RegExp(src, 'i')),
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
export function addUserProvider(input) {
|
|
14
|
+
const existing = loadSerialized();
|
|
15
|
+
const idx = existing.findIndex((p) => p.id === input.id);
|
|
16
|
+
if (idx >= 0) {
|
|
17
|
+
const p = existing[idx];
|
|
18
|
+
if (!p.patterns.includes(input.pattern)) {
|
|
19
|
+
p.patterns.push(input.pattern);
|
|
20
|
+
}
|
|
21
|
+
p.label = input.label;
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
existing.push({ id: input.id, label: input.label, patterns: [input.pattern] });
|
|
25
|
+
}
|
|
26
|
+
ensureDir();
|
|
27
|
+
writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(existing, null, 2), 'utf-8');
|
|
28
|
+
}
|
|
29
|
+
export function removeUserProvider(id) {
|
|
30
|
+
const existing = loadSerialized();
|
|
31
|
+
const filtered = existing.filter((p) => p.id !== id);
|
|
32
|
+
if (filtered.length === existing.length) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
ensureDir();
|
|
36
|
+
writeFileSync(USER_PROVIDERS_PATH, JSON.stringify(filtered, null, 2), 'utf-8');
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
export function listUserProviders() {
|
|
40
|
+
return loadSerialized();
|
|
41
|
+
}
|
|
42
|
+
function loadSerialized() {
|
|
43
|
+
if (!existsSync(USER_PROVIDERS_PATH)) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const parsed = JSON.parse(readFileSync(USER_PROVIDERS_PATH, 'utf-8'));
|
|
48
|
+
if (!Array.isArray(parsed)) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return [];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function ensureDir() {
|
|
58
|
+
const dir = dirname(USER_PROVIDERS_PATH);
|
|
59
|
+
if (!existsSync(dir)) {
|
|
60
|
+
mkdirSync(dir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import type { Gap } from '../schemas/gap-analysis.schema.js';
|
|
2
|
+
import type { StorageStateInvalidReason } from './auth-detector.js';
|
|
2
3
|
export declare function buildAuthBlockGap(url: string): Gap;
|
|
4
|
+
export declare function buildStorageStateInvalidGap(input: {
|
|
5
|
+
url: string;
|
|
6
|
+
reasonCode: StorageStateInvalidReason;
|
|
7
|
+
reason: string;
|
|
8
|
+
}): Gap;
|
|
3
9
|
//# sourceMappingURL=auth-block-gap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-block-gap.d.ts","sourceRoot":"","sources":["../../src/tools/auth-block-gap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mCAAmC,CAAC;
|
|
1
|
+
{"version":3,"file":"auth-block-gap.d.ts","sourceRoot":"","sources":["../../src/tools/auth-block-gap.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,mCAAmC,CAAC;AAC7D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,oBAAoB,CAAC;AAmBpE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAYlD;AAED,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,yBAAyB,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;CAChB,GAAG,GAAG,CAqBN"}
|
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
function safeOriginAndPath(url) {
|
|
2
|
+
try {
|
|
3
|
+
const u = new URL(url);
|
|
4
|
+
return `${u.origin}${u.pathname}`;
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return url;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
function safeHost(url) {
|
|
11
|
+
try {
|
|
12
|
+
return new URL(url).hostname;
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return url;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
1
18
|
export function buildAuthBlockGap(url) {
|
|
2
|
-
const host = (
|
|
3
|
-
|
|
4
|
-
return new URL(url).hostname;
|
|
5
|
-
}
|
|
6
|
-
catch {
|
|
7
|
-
return url;
|
|
8
|
-
}
|
|
9
|
-
})();
|
|
19
|
+
const host = safeHost(url);
|
|
20
|
+
const safeUrl = safeOriginAndPath(url);
|
|
10
21
|
return {
|
|
11
22
|
id: 'auth-block',
|
|
12
23
|
path: '/',
|
|
@@ -14,6 +25,28 @@ export function buildAuthBlockGap(url) {
|
|
|
14
25
|
category: 'coverage',
|
|
15
26
|
reason: `Scan blocked by authentication. No authenticated pages were evaluated for ${host}.`,
|
|
16
27
|
description: 'Scan blocked by authentication. 0 authenticated pages were evaluated.',
|
|
17
|
-
recommendation: `Run \`qulib auth init --base-url ${
|
|
28
|
+
recommendation: `Run \`qulib auth init --base-url ${safeUrl}\` to capture a storage state, then re-run with --auth storage-state.`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export function buildStorageStateInvalidGap(input) {
|
|
32
|
+
const host = safeHost(input.url);
|
|
33
|
+
const safeUrl = safeOriginAndPath(input.url);
|
|
34
|
+
const recoveryByCode = {
|
|
35
|
+
'missing-file': `Storage state file was not found. Run \`qulib auth login --base-url ${safeUrl} --out <path>\` (or \`qulib auth init\`) to capture a fresh state, then re-run \`qulib analyze --url ${safeUrl} --auth-storage-state <path>\`.`,
|
|
36
|
+
'unreadable-file': `Storage state file exists but could not be read. Check file permissions, then re-run \`qulib auth login\` if needed.`,
|
|
37
|
+
'invalid-json': `Storage state file is not valid JSON. Run \`qulib auth login --base-url ${safeUrl} --out <path>\` again to regenerate it.`,
|
|
38
|
+
'wrong-origin': `Storage state belongs to a different origin than ${host}. Re-run \`qulib auth login --base-url ${safeUrl}\` against this target and pass the new file to \`qulib analyze\`.`,
|
|
39
|
+
'expired-or-unauthorized': `The session in the storage state has expired or is unauthorized. Run \`qulib auth login --base-url ${safeUrl}\` to capture a fresh state, then re-run \`qulib analyze --url ${safeUrl} --auth-storage-state <path>\`.`,
|
|
40
|
+
'no-auth-cookies': `Storage state file contains no cookies or localStorage entries — it is effectively empty. Run \`qulib auth login --base-url ${safeUrl}\` to capture a real session.`,
|
|
41
|
+
unknown: `Storage state could not be validated. Try \`qulib auth login --base-url ${safeUrl}\` again, and verify the file was saved on the same origin.`,
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
id: 'storage-state-invalid',
|
|
45
|
+
path: '/',
|
|
46
|
+
severity: 'critical',
|
|
47
|
+
category: 'coverage',
|
|
48
|
+
reason: `Authenticated scan could not continue because the provided storage state is invalid for ${host}. Reason: ${input.reasonCode} — ${input.reason}.`,
|
|
49
|
+
description: `Storage state validation failed before crawling. The session was checked against ${host} and rejected with reason code "${input.reasonCode}".`,
|
|
50
|
+
recommendation: recoveryByCode[input.reasonCode],
|
|
18
51
|
};
|
|
19
52
|
}
|
|
@@ -1,22 +1,23 @@
|
|
|
1
1
|
import type { Page } from '@playwright/test';
|
|
2
2
|
import type { DetectedAuth } from '../schemas/config.schema.js';
|
|
3
3
|
import type { AnalyzeProgressSink } from '../harness/progress-log.js';
|
|
4
|
+
export type StorageStateInvalidReason = 'missing-file' | 'unreadable-file' | 'invalid-json' | 'wrong-origin' | 'expired-or-unauthorized' | 'no-auth-cookies' | 'unknown';
|
|
5
|
+
export interface StorageStateValidationResult {
|
|
6
|
+
valid: boolean;
|
|
7
|
+
reasonCode: StorageStateInvalidReason | 'ok';
|
|
8
|
+
reason: string;
|
|
9
|
+
}
|
|
4
10
|
export declare function evaluateStorageStateValidity(signals: {
|
|
5
11
|
expectedOrigin: string;
|
|
6
12
|
finalUrl: string;
|
|
7
13
|
visiblePasswordCount: number;
|
|
8
14
|
hadUnauthorizedHttp: boolean;
|
|
9
|
-
}):
|
|
10
|
-
|
|
11
|
-
reason: string;
|
|
12
|
-
};
|
|
15
|
+
}): StorageStateValidationResult;
|
|
16
|
+
export declare function preflightStorageStateFile(storagePath: string): Promise<StorageStateValidationResult | null>;
|
|
13
17
|
export declare function waitForReturnToOrigin(page: Page, baseUrl: string, timeoutMs?: number): Promise<{
|
|
14
18
|
returned: boolean;
|
|
15
19
|
finalUrl: string;
|
|
16
20
|
}>;
|
|
17
|
-
export declare function validateStorageState(url: string, storagePath: string, timeoutMs?: number): Promise<
|
|
18
|
-
valid: boolean;
|
|
19
|
-
reason: string;
|
|
20
|
-
}>;
|
|
21
|
+
export declare function validateStorageState(url: string, storagePath: string, timeoutMs?: number): Promise<StorageStateValidationResult>;
|
|
21
22
|
export declare function detectAuth(url: string, timeoutMs?: number, progress?: AnalyzeProgressSink): Promise<DetectedAuth>;
|
|
22
23
|
//# sourceMappingURL=auth-detector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-detector.d.ts","sourceRoot":"","sources":["../../src/tools/auth-detector.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth-detector.d.ts","sourceRoot":"","sources":["../../src/tools/auth-detector.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,EAAY,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC1E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AAItE,MAAM,MAAM,yBAAyB,GACjC,cAAc,GACd,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,yBAAyB,GACzB,iBAAiB,GACjB,SAAS,CAAC;AAEd,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,UAAU,EAAE,yBAAyB,GAAG,IAAI,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;CAChB;AAsQD,wBAAgB,4BAA4B,CAAC,OAAO,EAAE;IACpD,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,mBAAmB,EAAE,OAAO,CAAC;CAC9B,GAAG,4BAA4B,CA6B/B;AAOD,wBAAsB,yBAAyB,CAC7C,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,4BAA4B,GAAG,IAAI,CAAC,CAgD9C;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,SAAS,SAAQ,GAChB,OAAO,CAAC;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAelD;AAED,wBAAsB,oBAAoB,CACxC,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,MAAM,EACnB,SAAS,SAAQ,GAChB,OAAO,CAAC,4BAA4B,CAAC,CAyDvC;AAED,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EACX,SAAS,SAAQ,EACjB,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,OAAO,CAAC,YAAY,CAAC,CAqJvB"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolve } from 'node:path';
|
|
2
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
3
|
import { launchBrowser } from './browser.js';
|
|
3
4
|
import { BUILT_IN_OAUTH_PROVIDERS } from './oauth-providers.js';
|
|
4
5
|
async function waitNetworkIdleBestEffort(page) {
|
|
@@ -235,16 +236,87 @@ async function probeClickToRevealForms(page, loginUrl, alreadyMatchedTexts, time
|
|
|
235
236
|
return out;
|
|
236
237
|
}
|
|
237
238
|
export function evaluateStorageStateValidity(signals) {
|
|
238
|
-
|
|
239
|
-
|
|
239
|
+
let finalOrigin = null;
|
|
240
|
+
try {
|
|
241
|
+
finalOrigin = new URL(signals.finalUrl).origin;
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
finalOrigin = null;
|
|
245
|
+
}
|
|
246
|
+
if (finalOrigin === null || finalOrigin !== signals.expectedOrigin) {
|
|
247
|
+
return {
|
|
248
|
+
valid: false,
|
|
249
|
+
reasonCode: 'wrong-origin',
|
|
250
|
+
reason: 'session redirected to a different origin than the target app',
|
|
251
|
+
};
|
|
240
252
|
}
|
|
241
253
|
if (signals.visiblePasswordCount > 0) {
|
|
242
|
-
return {
|
|
254
|
+
return {
|
|
255
|
+
valid: false,
|
|
256
|
+
reasonCode: 'expired-or-unauthorized',
|
|
257
|
+
reason: 'login form still visible after loading storage state (session likely expired)',
|
|
258
|
+
};
|
|
243
259
|
}
|
|
244
260
|
if (signals.hadUnauthorizedHttp) {
|
|
245
|
-
return {
|
|
261
|
+
return {
|
|
262
|
+
valid: false,
|
|
263
|
+
reasonCode: 'expired-or-unauthorized',
|
|
264
|
+
reason: 'HTTP 401/403 on authenticated request (session likely expired or invalid)',
|
|
265
|
+
};
|
|
246
266
|
}
|
|
247
|
-
return { valid: true, reason: 'session appears active' };
|
|
267
|
+
return { valid: true, reasonCode: 'ok', reason: 'session appears active' };
|
|
268
|
+
}
|
|
269
|
+
export async function preflightStorageStateFile(storagePath) {
|
|
270
|
+
let exists = false;
|
|
271
|
+
try {
|
|
272
|
+
const s = await stat(storagePath);
|
|
273
|
+
exists = s.isFile();
|
|
274
|
+
}
|
|
275
|
+
catch {
|
|
276
|
+
exists = false;
|
|
277
|
+
}
|
|
278
|
+
if (!exists) {
|
|
279
|
+
return {
|
|
280
|
+
valid: false,
|
|
281
|
+
reasonCode: 'missing-file',
|
|
282
|
+
reason: 'storage state file does not exist',
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
let raw;
|
|
286
|
+
try {
|
|
287
|
+
raw = await readFile(storagePath, 'utf8');
|
|
288
|
+
}
|
|
289
|
+
catch {
|
|
290
|
+
return {
|
|
291
|
+
valid: false,
|
|
292
|
+
reasonCode: 'unreadable-file',
|
|
293
|
+
reason: 'storage state file is not readable',
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
let parsed;
|
|
297
|
+
try {
|
|
298
|
+
parsed = JSON.parse(raw);
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
return {
|
|
302
|
+
valid: false,
|
|
303
|
+
reasonCode: 'invalid-json',
|
|
304
|
+
reason: 'storage state file is not valid JSON',
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const cookieCount = Array.isArray(parsed?.cookies) ? parsed.cookies.length : 0;
|
|
308
|
+
const originEntries = Array.isArray(parsed?.origins) ? parsed.origins : [];
|
|
309
|
+
const localStorageEntryCount = originEntries.reduce((sum, entry) => {
|
|
310
|
+
return sum + (Array.isArray(entry?.localStorage) ? entry.localStorage.length : 0);
|
|
311
|
+
}, 0);
|
|
312
|
+
if (cookieCount === 0 && localStorageEntryCount === 0) {
|
|
313
|
+
return {
|
|
314
|
+
valid: false,
|
|
315
|
+
reasonCode: 'no-auth-cookies',
|
|
316
|
+
reason: 'storage state file contains no cookies and no localStorage entries',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
248
320
|
}
|
|
249
321
|
export async function waitForReturnToOrigin(page, baseUrl, timeoutMs = 15000) {
|
|
250
322
|
const targetOrigin = new URL(baseUrl).origin;
|
|
@@ -264,8 +336,22 @@ export async function waitForReturnToOrigin(page, baseUrl, timeoutMs = 15000) {
|
|
|
264
336
|
return { returned: false, finalUrl: page.url() };
|
|
265
337
|
}
|
|
266
338
|
export async function validateStorageState(url, storagePath, timeoutMs = 10000) {
|
|
339
|
+
let expectedOrigin;
|
|
340
|
+
try {
|
|
341
|
+
expectedOrigin = new URL(url).origin;
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return {
|
|
345
|
+
valid: false,
|
|
346
|
+
reasonCode: 'unknown',
|
|
347
|
+
reason: 'target URL could not be parsed for origin matching',
|
|
348
|
+
};
|
|
349
|
+
}
|
|
267
350
|
const storageAbs = resolve(process.cwd(), storagePath);
|
|
268
|
-
const
|
|
351
|
+
const preflight = await preflightStorageStateFile(storageAbs);
|
|
352
|
+
if (preflight !== null) {
|
|
353
|
+
return preflight;
|
|
354
|
+
}
|
|
269
355
|
let hadUnauthorizedHttp = false;
|
|
270
356
|
const browser = await launchBrowser();
|
|
271
357
|
try {
|
|
@@ -277,10 +363,22 @@ export async function validateStorageState(url, storagePath, timeoutMs = 10000)
|
|
|
277
363
|
hadUnauthorizedHttp = true;
|
|
278
364
|
}
|
|
279
365
|
});
|
|
280
|
-
|
|
366
|
+
try {
|
|
367
|
+
await page.goto(url, { timeout: timeoutMs, waitUntil: 'domcontentloaded' });
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
return {
|
|
371
|
+
valid: false,
|
|
372
|
+
reasonCode: 'unknown',
|
|
373
|
+
reason: 'navigation to target URL failed while validating storage state',
|
|
374
|
+
};
|
|
375
|
+
}
|
|
281
376
|
await waitNetworkIdleBestEffort(page);
|
|
282
377
|
const finalUrl = page.url();
|
|
283
|
-
const visiblePasswordCount = await page
|
|
378
|
+
const visiblePasswordCount = await page
|
|
379
|
+
.locator('input[type="password"]:visible')
|
|
380
|
+
.count()
|
|
381
|
+
.catch(() => 0);
|
|
284
382
|
return evaluateStorageStateValidity({
|
|
285
383
|
expectedOrigin,
|
|
286
384
|
finalUrl,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../../../src/tools/explorers/browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAE1D,wBAAsB,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC,CAYtD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { chromium } from '@playwright/test';
|
|
2
|
+
export async function launchBrowser() {
|
|
3
|
+
try {
|
|
4
|
+
return await chromium.launch({ headless: true });
|
|
5
|
+
}
|
|
6
|
+
catch (err) {
|
|
7
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
8
|
+
if (message.includes("Executable doesn't exist") || message.includes('chromium')) {
|
|
9
|
+
throw new Error(`Playwright Chromium browser is not installed. Run:\n\n npx playwright install chromium\n\nThen retry your qulib command. This is a one-time setup step.`);
|
|
10
|
+
}
|
|
11
|
+
throw err;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppExplorer } from './explorer.interface.js';
|
|
2
|
+
import type { HarnessConfig } from '../../schemas/config.schema.js';
|
|
3
|
+
import type { RouteInventory } from '../../schemas/route-inventory.schema.js';
|
|
4
|
+
import type { RunArtifactsOptions } from '../../harness/run-options.js';
|
|
5
|
+
export declare class CypressExplorer implements AppExplorer {
|
|
6
|
+
explore(_baseUrl: string, _config: HarnessConfig, _artifacts?: RunArtifactsOptions): Promise<RouteInventory>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=cypress-explorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cypress-explorer.d.ts","sourceRoot":"","sources":["../../../src/tools/explorers/cypress-explorer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAExE,qBAAa,eAAgB,YAAW,WAAW;IAC3C,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;CAGnH"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppExplorer } from './types.js';
|
|
2
|
+
import type { HarnessConfig } from '../../schemas/config.schema.js';
|
|
3
|
+
import type { RouteInventory } from '../../schemas/route-inventory.schema.js';
|
|
4
|
+
import type { RunArtifactsOptions } from '../../harness/run-options.js';
|
|
5
|
+
export declare class CypressExplorer implements AppExplorer {
|
|
6
|
+
explore(_baseUrl: string, _config: HarnessConfig, _artifacts?: RunArtifactsOptions): Promise<RouteInventory>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=cypress.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cypress.d.ts","sourceRoot":"","sources":["../../../src/tools/explorers/cypress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAExE,qBAAa,eAAgB,YAAW,WAAW;IAC3C,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,UAAU,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;CAGnH"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HarnessConfig } from '../../schemas/config.schema.js';
|
|
2
|
+
import type { RouteInventory } from '../../schemas/route-inventory.schema.js';
|
|
3
|
+
import type { RunArtifactsOptions } from '../../harness/run-options.js';
|
|
4
|
+
export interface AppExplorer {
|
|
5
|
+
explore(baseUrl: string, config: HarnessConfig, artifacts?: RunArtifactsOptions): Promise<RouteInventory>;
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=explorer.interface.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explorer.interface.d.ts","sourceRoot":"","sources":["../../../src/tools/explorers/explorer.interface.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,yCAAyC,CAAC;AAC9E,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAExE,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC,CAAC;CAC3G"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/tools/explorers/factory.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAI9C,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,WAAW,CAS9D"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { PlaywrightExplorer } from './playwright.js';
|
|
2
|
+
import { CypressExplorer } from './cypress.js';
|
|
3
|
+
export function createExplorer(type) {
|
|
4
|
+
switch (type) {
|
|
5
|
+
case 'playwright':
|
|
6
|
+
return new PlaywrightExplorer();
|
|
7
|
+
case 'cypress':
|
|
8
|
+
return new CypressExplorer();
|
|
9
|
+
default:
|
|
10
|
+
throw new Error(`Unknown explorer type: ${type}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AppExplorer } from './explorer.interface.js';
|
|
2
|
+
import { type RouteInventory } from '../../schemas/route-inventory.schema.js';
|
|
3
|
+
import type { HarnessConfig } from '../../schemas/config.schema.js';
|
|
4
|
+
import type { RunArtifactsOptions } from '../../harness/run-options.js';
|
|
5
|
+
export declare class PlaywrightExplorer implements AppExplorer {
|
|
6
|
+
explore(baseUrl: string, config: HarnessConfig, artifacts?: RunArtifactsOptions): Promise<RouteInventory>;
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=playwright-explorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"playwright-explorer.d.ts","sourceRoot":"","sources":["../../../src/tools/explorers/playwright-explorer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAwB,KAAK,cAAc,EAAc,MAAM,yCAAyC,CAAC;AAChH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AACpE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAoBxE,qBAAa,kBAAmB,YAAW,WAAW;IAC9C,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,cAAc,CAAC;CAsKhH"}
|