@ia-qa/self-healing 1.3.4 → 1.4.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.
@@ -0,0 +1,99 @@
1
+ import type { PageTarget } from '../config';
2
+ /**
3
+ * The safety floor shared by both discovery sources (sitemap + crawl).
4
+ *
5
+ * Discovery only ever *proposes* pages to add to `config.pages`; it never maps,
6
+ * heals, or edits a test. But the crawler navigates real URLs on a real (often
7
+ * authenticated) app, so the one thing that must be airtight is *which URLs it is
8
+ * allowed to touch at all*. That decision lives here, as pure functions, so it is
9
+ * unit-tested in isolation rather than trusted inside a browser loop.
10
+ *
11
+ * Two rules, both erring toward doing nothing:
12
+ * 1. Never leave the app's own origin.
13
+ * 2. Never touch a URL whose path or query reads like an action — logout, delete,
14
+ * pay, unsubscribe… A crawler that visits `/logout` kills the session for every
15
+ * page after it; one that visits `/orders/42/delete` (a GET that mutates — bad
16
+ * REST, but real) destroys data. Both are same-origin GETs a naive BFS would
17
+ * happily follow, so the guard is a denylist on the URL itself, not on a verb.
18
+ */
19
+ /**
20
+ * A path or query segment that names an action rather than a view. Matched against
21
+ * the full href (pathname + search), case-insensitive, on word-ish boundaries so
22
+ * `/deleted-items` (a view) is not caught by `delete` but `/item/delete` is.
23
+ *
24
+ * Deliberately broad: a false positive costs one un-discovered page (the user can
25
+ * add it by hand); a false negative can log the crawler out or mutate data. On an
26
+ * "ultra-safe" tool that trade is not close.
27
+ */
28
+ export declare const UNSAFE_URL_RE: RegExp;
29
+ export interface HostOptions {
30
+ /**
31
+ * Require the host to match `baseUrl`'s exactly. Off by default: `www.` is
32
+ * stripped from both sides first, so an apex `baseUrl` and a `www.` sitemap (or a
33
+ * `www.`→apex 301) are treated as one site. Turn this on to keep them distinct.
34
+ */
35
+ strictHost?: boolean;
36
+ }
37
+ /**
38
+ * The "same site" key of a URL: protocol + host, with a leading `www.` folded away
39
+ * unless `strictHost`. This is the whole apex/`www.` fix — comparing raw `origin`
40
+ * drops every `www.` sitemap URL when `baseUrl` is the apex (or vice versa), which
41
+ * is the single most common way a real `baseUrl` and a real sitemap differ. Protocol
42
+ * and port stay significant: `http` vs `https` is a real difference, not a `www.`.
43
+ */
44
+ export declare function siteKey(u: URL, strictHost?: boolean): string;
45
+ /** Why a URL was not kept — `host` is the one worth surfacing to the user. */
46
+ export type SkipReason = 'scheme' | 'host' | 'download' | 'action' | 'unparseable';
47
+ export interface UrlVerdict {
48
+ ok: boolean;
49
+ reason?: SkipReason;
50
+ /** The URL's host when it parsed — lets the caller name the mismatched host. */
51
+ host?: string;
52
+ }
53
+ /**
54
+ * Classify a URL against `baseUrl`, returning *why* it was rejected — so a caller
55
+ * can tell "on a different host" (worth a warning) from "a PDF" (silently skipped).
56
+ * The order matters: an off-host download reports `host`, the more actionable cause.
57
+ */
58
+ export declare function classifyUrl(href: string, baseUrl: string, opts?: HostOptions): UrlVerdict;
59
+ /**
60
+ * May the crawler touch this href, resolved against `baseUrl`?
61
+ *
62
+ * Returns false for anything off-site (see `siteKey` for the apex/`www.` rule),
63
+ * non-http, a download, or matching `UNSAFE_URL_RE`. Unparseable input is unsafe by
64
+ * default — the whole point is to be conservative about what gets a navigation.
65
+ */
66
+ export declare function isSafeCandidateUrl(href: string, baseUrl: string, opts?: HostOptions): boolean;
67
+ /**
68
+ * Compare two URLs by pathname alone — query and hash do not make another page,
69
+ * and a trailing slash is not a distinction. This is the dedup key for candidates,
70
+ * and mirrors `normalizePath` in overview.ts and `samePath` in map.ts so discovery,
71
+ * the nav graph, and `assertLanded` all agree on when two URLs are one page.
72
+ */
73
+ export declare function normalizedPath(href: string, baseUrl: string): string;
74
+ export interface Candidate {
75
+ name: string;
76
+ url: string;
77
+ /** Where this candidate came from — shown in the summary, never written to config. */
78
+ source: 'sitemap' | 'crawl';
79
+ }
80
+ /**
81
+ * Turn a raw URL into a candidate page, or null if it is not safe to propose.
82
+ *
83
+ * The stored `url` is the same shape the rest of the config uses: a path for
84
+ * same-origin (`/dashboard`), so a contract stays comparable across environments.
85
+ */
86
+ export declare function toCandidate(href: string, baseUrl: string, source: Candidate['source'], opts?: HostOptions): Candidate | null;
87
+ /**
88
+ * Fold candidates into the pages already in config, dropping duplicates and
89
+ * anything already configured, and making every new page name unique.
90
+ *
91
+ * A page name is a contract's filename ([[config.mappingPath]]), so a collision is
92
+ * not cosmetic — two pages would overwrite each other's mapping. Existing pages win
93
+ * their name; a new candidate that slugs to a taken name gets a numeric suffix.
94
+ * Dedup is by normalized path so `/x`, `/x/`, and `/x?ref=nav` are one page.
95
+ *
96
+ * Returns only the *new* pages to add, in discovery order, so the caller can show
97
+ * "found N, M new" and append them without disturbing the author's ordering.
98
+ */
99
+ export declare function mergePages(existing: PageTarget[], candidates: Candidate[], baseUrl: string): Candidate[];
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.UNSAFE_URL_RE = void 0;
4
+ exports.siteKey = siteKey;
5
+ exports.classifyUrl = classifyUrl;
6
+ exports.isSafeCandidateUrl = isSafeCandidateUrl;
7
+ exports.normalizedPath = normalizedPath;
8
+ exports.toCandidate = toCandidate;
9
+ exports.mergePages = mergePages;
10
+ const pageName_1 = require("../pageName");
11
+ /**
12
+ * The safety floor shared by both discovery sources (sitemap + crawl).
13
+ *
14
+ * Discovery only ever *proposes* pages to add to `config.pages`; it never maps,
15
+ * heals, or edits a test. But the crawler navigates real URLs on a real (often
16
+ * authenticated) app, so the one thing that must be airtight is *which URLs it is
17
+ * allowed to touch at all*. That decision lives here, as pure functions, so it is
18
+ * unit-tested in isolation rather than trusted inside a browser loop.
19
+ *
20
+ * Two rules, both erring toward doing nothing:
21
+ * 1. Never leave the app's own origin.
22
+ * 2. Never touch a URL whose path or query reads like an action — logout, delete,
23
+ * pay, unsubscribe… A crawler that visits `/logout` kills the session for every
24
+ * page after it; one that visits `/orders/42/delete` (a GET that mutates — bad
25
+ * REST, but real) destroys data. Both are same-origin GETs a naive BFS would
26
+ * happily follow, so the guard is a denylist on the URL itself, not on a verb.
27
+ */
28
+ /**
29
+ * A path or query segment that names an action rather than a view. Matched against
30
+ * the full href (pathname + search), case-insensitive, on word-ish boundaries so
31
+ * `/deleted-items` (a view) is not caught by `delete` but `/item/delete` is.
32
+ *
33
+ * Deliberately broad: a false positive costs one un-discovered page (the user can
34
+ * add it by hand); a false negative can log the crawler out or mutate data. On an
35
+ * "ultra-safe" tool that trade is not close.
36
+ */
37
+ exports.UNSAFE_URL_RE = /(?:^|[/_.?=&-])(?:log[\s_-]?out|sign[\s_-]?out|logout|signout|delete|destroy|remove|deactivate|disable|unsubscribe|revoke|cancel|purchase|checkout|payment|pay|buy|order|confirm|approve|reject|reset|logoff)(?:[/_.?=&-]|$)/i;
38
+ /** File extensions that are downloads, not pages — never worth mapping. */
39
+ const DOWNLOAD_EXT_RE = /\.(?:pdf|zip|gz|tar|rar|7z|csv|xlsx?|docx?|pptx?|png|jpe?g|gif|svg|webp|ico|mp[34]|mov|avi|woff2?|ttf|eot|dmg|exe|apk|pkg)(?:$|[?#])/i;
40
+ /**
41
+ * The "same site" key of a URL: protocol + host, with a leading `www.` folded away
42
+ * unless `strictHost`. This is the whole apex/`www.` fix — comparing raw `origin`
43
+ * drops every `www.` sitemap URL when `baseUrl` is the apex (or vice versa), which
44
+ * is the single most common way a real `baseUrl` and a real sitemap differ. Protocol
45
+ * and port stay significant: `http` vs `https` is a real difference, not a `www.`.
46
+ */
47
+ function siteKey(u, strictHost = false) {
48
+ const host = strictHost ? u.host : u.host.replace(/^www\./i, '');
49
+ return `${u.protocol}//${host}`;
50
+ }
51
+ /**
52
+ * Classify a URL against `baseUrl`, returning *why* it was rejected — so a caller
53
+ * can tell "on a different host" (worth a warning) from "a PDF" (silently skipped).
54
+ * The order matters: an off-host download reports `host`, the more actionable cause.
55
+ */
56
+ function classifyUrl(href, baseUrl, opts = {}) {
57
+ let url;
58
+ let base;
59
+ try {
60
+ base = new URL(baseUrl);
61
+ url = new URL(href, baseUrl);
62
+ }
63
+ catch {
64
+ return { ok: false, reason: 'unparseable' };
65
+ }
66
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
67
+ return { ok: false, reason: 'scheme' };
68
+ if (siteKey(url, opts.strictHost) !== siteKey(base, opts.strictHost)) {
69
+ return { ok: false, reason: 'host', host: url.host };
70
+ }
71
+ if (DOWNLOAD_EXT_RE.test(url.pathname + url.search))
72
+ return { ok: false, reason: 'download', host: url.host };
73
+ if (exports.UNSAFE_URL_RE.test(url.pathname + url.search))
74
+ return { ok: false, reason: 'action', host: url.host };
75
+ return { ok: true, host: url.host };
76
+ }
77
+ /**
78
+ * May the crawler touch this href, resolved against `baseUrl`?
79
+ *
80
+ * Returns false for anything off-site (see `siteKey` for the apex/`www.` rule),
81
+ * non-http, a download, or matching `UNSAFE_URL_RE`. Unparseable input is unsafe by
82
+ * default — the whole point is to be conservative about what gets a navigation.
83
+ */
84
+ function isSafeCandidateUrl(href, baseUrl, opts = {}) {
85
+ return classifyUrl(href, baseUrl, opts).ok;
86
+ }
87
+ /**
88
+ * Compare two URLs by pathname alone — query and hash do not make another page,
89
+ * and a trailing slash is not a distinction. This is the dedup key for candidates,
90
+ * and mirrors `normalizePath` in overview.ts and `samePath` in map.ts so discovery,
91
+ * the nav graph, and `assertLanded` all agree on when two URLs are one page.
92
+ */
93
+ function normalizedPath(href, baseUrl) {
94
+ let url;
95
+ try {
96
+ url = new URL(href, baseUrl);
97
+ }
98
+ catch {
99
+ return href;
100
+ }
101
+ const p = url.pathname;
102
+ return p.length > 1 && p.endsWith('/') ? p.slice(0, -1) : p;
103
+ }
104
+ /**
105
+ * Turn a raw URL into a candidate page, or null if it is not safe to propose.
106
+ *
107
+ * The stored `url` is the same shape the rest of the config uses: a path for
108
+ * same-origin (`/dashboard`), so a contract stays comparable across environments.
109
+ */
110
+ function toCandidate(href, baseUrl, source, opts = {}) {
111
+ if (!isSafeCandidateUrl(href, baseUrl, opts))
112
+ return null;
113
+ const path = normalizedPath(href, baseUrl);
114
+ return { name: (0, pageName_1.pageNameFromUrl)(new URL(href, baseUrl).toString()), url: path, source };
115
+ }
116
+ /**
117
+ * Fold candidates into the pages already in config, dropping duplicates and
118
+ * anything already configured, and making every new page name unique.
119
+ *
120
+ * A page name is a contract's filename ([[config.mappingPath]]), so a collision is
121
+ * not cosmetic — two pages would overwrite each other's mapping. Existing pages win
122
+ * their name; a new candidate that slugs to a taken name gets a numeric suffix.
123
+ * Dedup is by normalized path so `/x`, `/x/`, and `/x?ref=nav` are one page.
124
+ *
125
+ * Returns only the *new* pages to add, in discovery order, so the caller can show
126
+ * "found N, M new" and append them without disturbing the author's ordering.
127
+ */
128
+ function mergePages(existing, candidates, baseUrl) {
129
+ const takenPaths = new Set(existing
130
+ .filter((p) => !p.steps?.length)
131
+ .map((p) => normalizedPath(p.url, baseUrl)));
132
+ const takenNames = new Set(existing.map((p) => p.name));
133
+ const added = [];
134
+ for (const c of candidates) {
135
+ const key = normalizedPath(c.url, baseUrl);
136
+ if (takenPaths.has(key))
137
+ continue;
138
+ takenPaths.add(key);
139
+ let name = c.name;
140
+ if (takenNames.has(name)) {
141
+ let n = 2;
142
+ while (takenNames.has(`${name}-${n}`))
143
+ n++;
144
+ name = `${name}-${n}`;
145
+ }
146
+ takenNames.add(name);
147
+ added.push({ ...c, name });
148
+ }
149
+ return added;
150
+ }
151
+ //# sourceMappingURL=safety.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"safety.js","sourceRoot":"","sources":["../../src/discovery/safety.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ import { Candidate, HostOptions } from './safety';
2
+ /**
3
+ * Sitemap discovery — the zero-risk source.
4
+ *
5
+ * A sitemap is a manifest the site *publishes about itself*, so reading it is a
6
+ * single GET and never touches the app's state: no browser, no login, no clicks.
7
+ * Its blind spot is the mirror image of the crawler's: it sees the public, SEO
8
+ * surface and stops exactly where the authenticated app begins. That is why the
9
+ * two exist side by side rather than one replacing the other.
10
+ *
11
+ * Dependency-free by the same rule as `browser/extract.js`: `<loc>` extraction is a
12
+ * regex, not an XML library, because namespaces vary between generators and the
13
+ * data we need is trivial. `fetch` is injectable so the parser is tested without a
14
+ * network.
15
+ */
16
+ export type FetchLike = (url: string) => Promise<{
17
+ ok: boolean;
18
+ status: number;
19
+ text: () => Promise<string>;
20
+ }>;
21
+ export interface SitemapOptions extends HostOptions {
22
+ /** Explicit sitemap URL. Defaults to `<baseUrl>/sitemap.xml`, then robots.txt. */
23
+ sitemapUrl?: string;
24
+ /** Injected for tests; defaults to the global `fetch` (Node 18+). */
25
+ fetchImpl?: FetchLike;
26
+ /** Hard cap on URLs returned, to keep a 10k-URL catalogue from flooding config. */
27
+ maxUrls?: number;
28
+ /** Bound on nested sitemap files followed from an index. */
29
+ maxSitemaps?: number;
30
+ }
31
+ /**
32
+ * An honest account of what the sitemap held versus what survived — the fix for the
33
+ * silent "0 URLs" that a host mismatch used to produce. `parsed` is every page
34
+ * `<loc>` seen; the three skip buckets say why the rest did not make it, and
35
+ * `otherHosts` names the hosts behind a `host` skip so the CLI can point at the
36
+ * apex/`www.` mismatch by name instead of leaving the user to guess.
37
+ */
38
+ export interface SitemapStats {
39
+ parsed: number;
40
+ retained: number;
41
+ skippedHost: number;
42
+ skippedOther: number;
43
+ otherHosts: string[];
44
+ }
45
+ export interface SitemapResult {
46
+ candidates: Candidate[];
47
+ stats: SitemapStats;
48
+ }
49
+ /** Pull every `<loc>` out of a sitemap or sitemap-index document. Pure. */
50
+ export declare function extractLocs(xml: string): string[];
51
+ /** A sitemap index points at other sitemaps rather than at pages. */
52
+ export declare function isSitemapIndex(xml: string): boolean;
53
+ /** `Sitemap:` directives in a robots.txt — the standard way to advertise one. */
54
+ export declare function sitemapsFromRobots(robots: string): string[];
55
+ /**
56
+ * Discover candidate pages from a site's sitemap(s).
57
+ *
58
+ * Resolution: the explicit `sitemapUrl` if given, else `/sitemap.xml`; if that
59
+ * 404s, the `Sitemap:` lines in `/robots.txt`. A sitemap index is followed one
60
+ * level into its child sitemaps (bounded by `maxSitemaps`). Every page URL is
61
+ * classified (see `classifyUrl`), so off-site, unsafe, and download URLs never
62
+ * survive — but the count of what was dropped, and why, comes back in `stats` so
63
+ * the caller can tell a host mismatch from "nothing there" instead of printing a
64
+ * silent "0 URLs".
65
+ *
66
+ * Never throws on a missing or malformed sitemap: absence is the common case
67
+ * (SPAs, staging), and it is a "found nothing", not an error.
68
+ */
69
+ export declare function discoverFromSitemap(baseUrl: string, options?: SitemapOptions): Promise<SitemapResult>;
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractLocs = extractLocs;
4
+ exports.isSitemapIndex = isSitemapIndex;
5
+ exports.sitemapsFromRobots = sitemapsFromRobots;
6
+ exports.discoverFromSitemap = discoverFromSitemap;
7
+ const safety_1 = require("./safety");
8
+ const pageName_1 = require("../pageName");
9
+ const LOC_RE = /<loc>\s*([^<\s][^<]*?)\s*<\/loc>/gi;
10
+ /** Pull every `<loc>` out of a sitemap or sitemap-index document. Pure. */
11
+ function extractLocs(xml) {
12
+ const out = [];
13
+ let m;
14
+ LOC_RE.lastIndex = 0;
15
+ while ((m = LOC_RE.exec(xml)) !== null) {
16
+ const loc = decodeEntities(m[1].trim());
17
+ if (loc)
18
+ out.push(loc);
19
+ }
20
+ return out;
21
+ }
22
+ /** A sitemap index points at other sitemaps rather than at pages. */
23
+ function isSitemapIndex(xml) {
24
+ return /<sitemapindex[\s>]/i.test(xml);
25
+ }
26
+ function decodeEntities(s) {
27
+ return s
28
+ .replace(/&amp;/g, '&')
29
+ .replace(/&lt;/g, '<')
30
+ .replace(/&gt;/g, '>')
31
+ .replace(/&quot;/g, '"')
32
+ .replace(/&#39;/g, "'");
33
+ }
34
+ /** `Sitemap:` directives in a robots.txt — the standard way to advertise one. */
35
+ function sitemapsFromRobots(robots) {
36
+ const out = [];
37
+ for (const line of robots.split(/\r?\n/)) {
38
+ const m = /^\s*sitemap:\s*(\S+)/i.exec(line);
39
+ if (m)
40
+ out.push(m[1].trim());
41
+ }
42
+ return out;
43
+ }
44
+ /**
45
+ * Discover candidate pages from a site's sitemap(s).
46
+ *
47
+ * Resolution: the explicit `sitemapUrl` if given, else `/sitemap.xml`; if that
48
+ * 404s, the `Sitemap:` lines in `/robots.txt`. A sitemap index is followed one
49
+ * level into its child sitemaps (bounded by `maxSitemaps`). Every page URL is
50
+ * classified (see `classifyUrl`), so off-site, unsafe, and download URLs never
51
+ * survive — but the count of what was dropped, and why, comes back in `stats` so
52
+ * the caller can tell a host mismatch from "nothing there" instead of printing a
53
+ * silent "0 URLs".
54
+ *
55
+ * Never throws on a missing or malformed sitemap: absence is the common case
56
+ * (SPAs, staging), and it is a "found nothing", not an error.
57
+ */
58
+ async function discoverFromSitemap(baseUrl, options = {}) {
59
+ const fetchImpl = options.fetchImpl || globalThis.fetch;
60
+ if (!fetchImpl)
61
+ throw new Error('No fetch implementation available (Node 18+ or pass fetchImpl).');
62
+ const maxUrls = options.maxUrls ?? 200;
63
+ const maxSitemaps = options.maxSitemaps ?? 20;
64
+ const roots = options.sitemapUrl
65
+ ? [options.sitemapUrl]
66
+ : await resolveSitemapUrls(baseUrl, fetchImpl);
67
+ const seenSitemaps = new Set();
68
+ const pageUrls = [];
69
+ const queue = [...roots];
70
+ while (queue.length > 0 && seenSitemaps.size < maxSitemaps && pageUrls.length < maxUrls) {
71
+ const url = queue.shift();
72
+ if (seenSitemaps.has(url))
73
+ continue;
74
+ seenSitemaps.add(url);
75
+ const xml = await safeText(fetchImpl, url);
76
+ if (!xml)
77
+ continue;
78
+ if (isSitemapIndex(xml)) {
79
+ for (const child of extractLocs(xml)) {
80
+ if (!seenSitemaps.has(child))
81
+ queue.push(child);
82
+ }
83
+ }
84
+ else {
85
+ pageUrls.push(...extractLocs(xml));
86
+ }
87
+ }
88
+ const candidates = [];
89
+ const seenPaths = new Set();
90
+ const otherHosts = new Set();
91
+ const stats = { parsed: 0, retained: 0, skippedHost: 0, skippedOther: 0, otherHosts: [] };
92
+ for (const raw of pageUrls) {
93
+ stats.parsed++;
94
+ const verdict = (0, safety_1.classifyUrl)(raw, baseUrl, { strictHost: options.strictHost });
95
+ if (!verdict.ok) {
96
+ if (verdict.reason === 'host') {
97
+ stats.skippedHost++;
98
+ if (verdict.host)
99
+ otherHosts.add(verdict.host);
100
+ }
101
+ else {
102
+ stats.skippedOther++;
103
+ }
104
+ continue;
105
+ }
106
+ const path = (0, safety_1.normalizedPath)(raw, baseUrl);
107
+ if (seenPaths.has(path)) {
108
+ stats.skippedOther++;
109
+ continue;
110
+ }
111
+ seenPaths.add(path);
112
+ candidates.push({ name: (0, pageName_1.pageNameFromUrl)(new URL(raw, baseUrl).toString()), url: path, source: 'sitemap' });
113
+ stats.retained++;
114
+ if (candidates.length >= maxUrls)
115
+ break;
116
+ }
117
+ stats.otherHosts = Array.from(otherHosts).slice(0, 5);
118
+ return { candidates, stats };
119
+ }
120
+ async function resolveSitemapUrls(baseUrl, fetchImpl) {
121
+ const primary = new URL('/sitemap.xml', baseUrl).toString();
122
+ const head = await safeText(fetchImpl, primary);
123
+ if (head)
124
+ return [primary];
125
+ const robots = await safeText(fetchImpl, new URL('/robots.txt', baseUrl).toString());
126
+ if (robots) {
127
+ const advertised = sitemapsFromRobots(robots);
128
+ if (advertised.length > 0)
129
+ return advertised;
130
+ }
131
+ return [primary];
132
+ }
133
+ async function safeText(fetchImpl, url) {
134
+ try {
135
+ const res = await fetchImpl(url);
136
+ if (!res.ok)
137
+ return null;
138
+ return await res.text();
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ //# sourceMappingURL=sitemap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sitemap.js","sourceRoot":"","sources":["../../src/discovery/sitemap.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -9,6 +9,12 @@ export type { MappedElement, PageMapping } from './aom';
9
9
  export { renderPageContract, saveMarkdown, markdownPath } from './markdown';
10
10
  export { mapUrl, pageNameFromUrl } from './mapUrl';
11
11
  export { resolvePageName } from './pageName';
12
+ export { isSafeCandidateUrl, normalizedPath, toCandidate, mergePages, UNSAFE_URL_RE, } from './discovery/safety';
13
+ export type { Candidate, HostOptions } from './discovery/safety';
14
+ export { discoverFromSitemap, extractLocs, isSitemapIndex, sitemapsFromRobots, } from './discovery/sitemap';
15
+ export type { SitemapOptions, SitemapResult, SitemapStats, FetchLike } from './discovery/sitemap';
16
+ export { crawlApp } from './crawl';
17
+ export type { CrawlOptions } from './crawl';
12
18
  export type { PageNameResolution } from './pageName';
13
19
  export { scanText, ingestPaths, saveUsage, loadUsage, usageFiles, usagePath } from './ingest';
14
20
  export type { Usage, SelectorSite, SelectorShape } from './ingest';
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.navigationGraph = exports.layoutGraph = exports.renderNavigationSvg = exports.renderNavigation = exports.renderOverview = exports.buildOverview = exports.minPagesForLayout = exports.stripLayoutFromPages = exports.extractLayout = exports.LAYOUT_DIRNAME = exports.layoutPath = exports.layoutDir = exports.resolveSecret = exports.loadConfig = exports.replaceQuoted = exports.collectFiles = exports.applyRewrites = exports.computeRewrites = exports.launchBrowser = exports.CAPTURE_DIRNAME = exports.captureDir = exports.unionElements = exports.mergeCaptures = exports.usagePath = exports.usageFiles = exports.loadUsage = exports.saveUsage = exports.ingestPaths = exports.scanText = exports.resolvePageName = exports.pageNameFromUrl = exports.mapUrl = exports.markdownPath = exports.saveMarkdown = exports.renderPageContract = exports.saveMapping = exports.loadMapping = exports.extractInteractiveElements = exports.renderModelList = exports.defaultKeyEnv = exports.findProvider = exports.AI_PROVIDERS = exports.parseModelJson = exports.buildPrompt = exports.prefilter = exports.aiResolve = exports.createAiResolver = exports.llmResolverStub = exports.aiFill = exports.aiClick = void 0;
4
- exports.NAVIGATION_SVG_FILENAME = exports.NAVIGATION_FILENAME = exports.OVERVIEW_FILENAME = exports.navigationSvgPath = exports.navigationPath = exports.overviewPath = exports.writeOverview = void 0;
3
+ exports.layoutPath = exports.layoutDir = exports.resolveSecret = exports.loadConfig = exports.replaceQuoted = exports.collectFiles = exports.applyRewrites = exports.computeRewrites = exports.launchBrowser = exports.CAPTURE_DIRNAME = exports.captureDir = exports.unionElements = exports.mergeCaptures = exports.usagePath = exports.usageFiles = exports.loadUsage = exports.saveUsage = exports.ingestPaths = exports.scanText = exports.crawlApp = exports.sitemapsFromRobots = exports.isSitemapIndex = exports.extractLocs = exports.discoverFromSitemap = exports.UNSAFE_URL_RE = exports.mergePages = exports.toCandidate = exports.normalizedPath = exports.isSafeCandidateUrl = exports.resolvePageName = exports.pageNameFromUrl = exports.mapUrl = exports.markdownPath = exports.saveMarkdown = exports.renderPageContract = exports.saveMapping = exports.loadMapping = exports.extractInteractiveElements = exports.renderModelList = exports.defaultKeyEnv = exports.findProvider = exports.AI_PROVIDERS = exports.parseModelJson = exports.buildPrompt = exports.prefilter = exports.aiResolve = exports.createAiResolver = exports.llmResolverStub = exports.aiFill = exports.aiClick = void 0;
4
+ exports.NAVIGATION_SVG_FILENAME = exports.NAVIGATION_FILENAME = exports.OVERVIEW_FILENAME = exports.navigationSvgPath = exports.navigationPath = exports.overviewPath = exports.writeOverview = exports.navigationGraph = exports.layoutGraph = exports.renderNavigationSvg = exports.renderNavigation = exports.renderOverview = exports.buildOverview = exports.minPagesForLayout = exports.stripLayoutFromPages = exports.extractLayout = exports.LAYOUT_DIRNAME = void 0;
5
5
  var healer_1 = require("./playwright/healer");
6
6
  Object.defineProperty(exports, "aiClick", { enumerable: true, get: function () { return healer_1.aiClick; } });
7
7
  Object.defineProperty(exports, "aiFill", { enumerable: true, get: function () { return healer_1.aiFill; } });
@@ -30,6 +30,19 @@ Object.defineProperty(exports, "mapUrl", { enumerable: true, get: function () {
30
30
  Object.defineProperty(exports, "pageNameFromUrl", { enumerable: true, get: function () { return mapUrl_1.pageNameFromUrl; } });
31
31
  var pageName_1 = require("./pageName");
32
32
  Object.defineProperty(exports, "resolvePageName", { enumerable: true, get: function () { return pageName_1.resolvePageName; } });
33
+ var safety_1 = require("./discovery/safety");
34
+ Object.defineProperty(exports, "isSafeCandidateUrl", { enumerable: true, get: function () { return safety_1.isSafeCandidateUrl; } });
35
+ Object.defineProperty(exports, "normalizedPath", { enumerable: true, get: function () { return safety_1.normalizedPath; } });
36
+ Object.defineProperty(exports, "toCandidate", { enumerable: true, get: function () { return safety_1.toCandidate; } });
37
+ Object.defineProperty(exports, "mergePages", { enumerable: true, get: function () { return safety_1.mergePages; } });
38
+ Object.defineProperty(exports, "UNSAFE_URL_RE", { enumerable: true, get: function () { return safety_1.UNSAFE_URL_RE; } });
39
+ var sitemap_1 = require("./discovery/sitemap");
40
+ Object.defineProperty(exports, "discoverFromSitemap", { enumerable: true, get: function () { return sitemap_1.discoverFromSitemap; } });
41
+ Object.defineProperty(exports, "extractLocs", { enumerable: true, get: function () { return sitemap_1.extractLocs; } });
42
+ Object.defineProperty(exports, "isSitemapIndex", { enumerable: true, get: function () { return sitemap_1.isSitemapIndex; } });
43
+ Object.defineProperty(exports, "sitemapsFromRobots", { enumerable: true, get: function () { return sitemap_1.sitemapsFromRobots; } });
44
+ var crawl_1 = require("./crawl");
45
+ Object.defineProperty(exports, "crawlApp", { enumerable: true, get: function () { return crawl_1.crawlApp; } });
33
46
  var ingest_1 = require("./ingest");
34
47
  Object.defineProperty(exports, "scanText", { enumerable: true, get: function () { return ingest_1.scanText; } });
35
48
  Object.defineProperty(exports, "ingestPaths", { enumerable: true, get: function () { return ingest_1.ingestPaths; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;AAAA,8CAAyF;AAAhF,iGAAA,OAAO,OAAA;AAAE,gGAAA,MAAM,OAAA;AAAE,yGAAA,eAAe,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AAE3D,0CAAkF;AAAzE,qGAAA,SAAS,OAAA;AAAE,qGAAA,SAAS,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,0GAAA,cAAc,OAAA;AAE1D,sCAAyF;AAAhF,sGAAA,YAAY,OAAA;AAAE,sGAAA,YAAY,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,yGAAA,eAAe,OAAA;AAEnE,6BAA6E;AAApE,iHAAA,0BAA0B,OAAA;AAAE,kGAAA,WAAW,OAAA;AAAE,kGAAA,WAAW,OAAA;AAE7D,uCAA4E;AAAnE,8GAAA,kBAAkB,OAAA;AAAE,wGAAA,YAAY,OAAA;AAAE,wGAAA,YAAY,OAAA;AACvD,mCAAmD;AAA1C,gGAAA,MAAM,OAAA;AAAE,yGAAA,eAAe,OAAA;AAChC,uCAA6C;AAApC,2GAAA,eAAe,OAAA;AAExB,mCAA8F;AAArF,kGAAA,QAAQ,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,mGAAA,SAAS,OAAA;AAE3E,+CAA2F;AAAlF,6GAAA,aAAa,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,0GAAA,UAAU,OAAA;AAAE,+GAAA,eAAe,OAAA;AAElE,uCAA2C;AAAlC,yGAAA,aAAa,OAAA;AAGtB,yCAA0F;AAAjF,4GAAA,eAAe,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,0GAAA,aAAa,OAAA;AACpE,mCAA4F;AAAnF,oGAAA,UAAU,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,wGAAA,cAAc,OAAA;AAEzE,mCAAkF;AAAzE,uGAAA,aAAa,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAE/D,uCAcoB;AAblB,yGAAA,aAAa,OAAA;AACb,0GAAA,cAAc,OAAA;AACd,4GAAA,gBAAgB,OAAA;AAChB,+GAAA,mBAAmB,OAAA;AACnB,uGAAA,WAAW,OAAA;AACX,2GAAA,eAAe,OAAA;AACf,yGAAA,aAAa,OAAA;AACb,wGAAA,YAAY,OAAA;AACZ,0GAAA,cAAc,OAAA;AACd,6GAAA,iBAAiB,OAAA;AACjB,6GAAA,iBAAiB,OAAA;AACjB,+GAAA,mBAAmB,OAAA;AACnB,mHAAA,uBAAuB,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;AAAA,8CAAyF;AAAhF,iGAAA,OAAO,OAAA;AAAE,gGAAA,MAAM,OAAA;AAAE,yGAAA,eAAe,OAAA;AAAE,0GAAA,gBAAgB,OAAA;AAE3D,0CAAkF;AAAzE,qGAAA,SAAS,OAAA;AAAE,qGAAA,SAAS,OAAA;AAAE,uGAAA,WAAW,OAAA;AAAE,0GAAA,cAAc,OAAA;AAE1D,sCAAyF;AAAhF,sGAAA,YAAY,OAAA;AAAE,sGAAA,YAAY,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,yGAAA,eAAe,OAAA;AAEnE,6BAA6E;AAApE,iHAAA,0BAA0B,OAAA;AAAE,kGAAA,WAAW,OAAA;AAAE,kGAAA,WAAW,OAAA;AAE7D,uCAA4E;AAAnE,8GAAA,kBAAkB,OAAA;AAAE,wGAAA,YAAY,OAAA;AAAE,wGAAA,YAAY,OAAA;AACvD,mCAAmD;AAA1C,gGAAA,MAAM,OAAA;AAAE,yGAAA,eAAe,OAAA;AAChC,uCAA6C;AAApC,2GAAA,eAAe,OAAA;AACxB,6CAM4B;AAL1B,4GAAA,kBAAkB,OAAA;AAClB,wGAAA,cAAc,OAAA;AACd,qGAAA,WAAW,OAAA;AACX,oGAAA,UAAU,OAAA;AACV,uGAAA,aAAa,OAAA;AAGf,+CAK6B;AAJ3B,8GAAA,mBAAmB,OAAA;AACnB,sGAAA,WAAW,OAAA;AACX,yGAAA,cAAc,OAAA;AACd,6GAAA,kBAAkB,OAAA;AAGpB,iCAAmC;AAA1B,iGAAA,QAAQ,OAAA;AAGjB,mCAA8F;AAArF,kGAAA,QAAQ,OAAA;AAAE,qGAAA,WAAW,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,mGAAA,SAAS,OAAA;AAE3E,+CAA2F;AAAlF,6GAAA,aAAa,OAAA;AAAE,6GAAA,aAAa,OAAA;AAAE,0GAAA,UAAU,OAAA;AAAE,+GAAA,eAAe,OAAA;AAElE,uCAA2C;AAAlC,yGAAA,aAAa,OAAA;AAGtB,yCAA0F;AAAjF,4GAAA,eAAe,OAAA;AAAE,0GAAA,aAAa,OAAA;AAAE,yGAAA,YAAY,OAAA;AAAE,0GAAA,aAAa,OAAA;AACpE,mCAA4F;AAAnF,oGAAA,UAAU,OAAA;AAAE,uGAAA,aAAa,OAAA;AAAE,mGAAA,SAAS,OAAA;AAAE,oGAAA,UAAU,OAAA;AAAE,wGAAA,cAAc,OAAA;AAEzE,mCAAkF;AAAzE,uGAAA,aAAa,OAAA;AAAE,8GAAA,oBAAoB,OAAA;AAAE,2GAAA,iBAAiB,OAAA;AAE/D,uCAcoB;AAblB,yGAAA,aAAa,OAAA;AACb,0GAAA,cAAc,OAAA;AACd,4GAAA,gBAAgB,OAAA;AAChB,+GAAA,mBAAmB,OAAA;AACnB,uGAAA,WAAW,OAAA;AACX,2GAAA,eAAe,OAAA;AACf,yGAAA,aAAa,OAAA;AACb,wGAAA,YAAY,OAAA;AACZ,0GAAA,cAAc,OAAA;AACd,6GAAA,iBAAiB,OAAA;AACjB,6GAAA,iBAAiB,OAAA;AACjB,+GAAA,mBAAmB,OAAA;AACnB,mHAAA,uBAAuB,OAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ia-qa/self-healing",
3
- "version": "1.3.4",
3
+ "version": "1.4.0",
4
4
  "description": "Local-first self-healing for UI tests: a local MCP server + CLI that map your app's pages to a role/name/selector contract, diff selector drift (PASS/FIX/BLOCK), and apply deterministic fixes to Cypress/Playwright/Selenium tests. Runs on your machine — nothing leaves it.",
5
5
  "keywords": [
6
6
  "self-healing",