@goflag/cli 0.2.2 → 0.2.4
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/dist/{chunk-6WOWKLXY.js → chunk-TVJVVSLV.js} +153 -18
- package/dist/chunk-TVJVVSLV.js.map +1 -0
- package/dist/cli.js +25 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +34 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-6WOWKLXY.js.map +0 -1
|
@@ -1925,10 +1925,10 @@ function dedupeBy(items, key) {
|
|
|
1925
1925
|
return out;
|
|
1926
1926
|
}
|
|
1927
1927
|
function splitRoute(pathname) {
|
|
1928
|
-
const
|
|
1929
|
-
if (
|
|
1930
|
-
const locale = localeIdentity(
|
|
1931
|
-
const rest =
|
|
1928
|
+
const segments2 = pathname.split("/").filter(Boolean);
|
|
1929
|
+
if (segments2.length > 0 && looksLikeLocaleSegment(segments2[0])) {
|
|
1930
|
+
const locale = localeIdentity(segments2[0]);
|
|
1931
|
+
const rest = segments2.slice(1).join("/");
|
|
1932
1932
|
return { route: rest ? `/${rest}` : "/", locale };
|
|
1933
1933
|
}
|
|
1934
1934
|
return { route: pathname || "/", locale: "x-default" };
|
|
@@ -2289,7 +2289,12 @@ function parseSitemap(body) {
|
|
|
2289
2289
|
|
|
2290
2290
|
// src/lib/core/sitemap/discover.ts
|
|
2291
2291
|
var DEFAULTS = {
|
|
2292
|
-
|
|
2292
|
+
// A sitemap is one document and it can be large: this repository's own
|
|
2293
|
+
// openfinanceguide serves 3.5 MB across 4008 URLs, and that took 1.2 s on a
|
|
2294
|
+
// good connection. Eight seconds was tight enough that the fetch failed
|
|
2295
|
+
// intermittently, and every failure silently dropped the crawl to link
|
|
2296
|
+
// following. Twenty is generous for a document fetched once per run.
|
|
2297
|
+
timeoutMs: 2e4,
|
|
2293
2298
|
maxUrls: 5e3,
|
|
2294
2299
|
maxSitemaps: 50,
|
|
2295
2300
|
crawlDepth: 1,
|
|
@@ -2332,6 +2337,10 @@ async function discoverSitemap(baseUrl, options = {}) {
|
|
|
2332
2337
|
const doc = await fetchDoc(candidate.url, options);
|
|
2333
2338
|
if (!doc.body) {
|
|
2334
2339
|
if (diagnostics.status === 0) diagnostics.status = doc.status;
|
|
2340
|
+
if (doc.error) {
|
|
2341
|
+
diagnostics.unreachable ??= `${candidate.url}: ${doc.error}`;
|
|
2342
|
+
diagnostics.warnings.push(`Sitemap unreachable at ${candidate.url} \u2014 ${doc.error}`);
|
|
2343
|
+
}
|
|
2335
2344
|
continue;
|
|
2336
2345
|
}
|
|
2337
2346
|
const parsed = parseSitemap(doc.body);
|
|
@@ -2472,8 +2481,9 @@ async function fetchDoc(url, options) {
|
|
|
2472
2481
|
}
|
|
2473
2482
|
}
|
|
2474
2483
|
return { status: res.status, body: await res.text() };
|
|
2475
|
-
} catch {
|
|
2476
|
-
|
|
2484
|
+
} catch (err) {
|
|
2485
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
2486
|
+
return { status: 0, error: reason || "network error" };
|
|
2477
2487
|
} finally {
|
|
2478
2488
|
cleanup();
|
|
2479
2489
|
restoreTls();
|
|
@@ -2489,6 +2499,90 @@ function relaxTlsIfRequested2(enabled) {
|
|
|
2489
2499
|
};
|
|
2490
2500
|
}
|
|
2491
2501
|
|
|
2502
|
+
// src/lib/core/coverage.ts
|
|
2503
|
+
var DEFAULT_PER_FAMILY = 3;
|
|
2504
|
+
var DEFAULT_THRESHOLD = 8;
|
|
2505
|
+
function segments(url) {
|
|
2506
|
+
try {
|
|
2507
|
+
return new URL(url).pathname.split("/").filter(Boolean);
|
|
2508
|
+
} catch {
|
|
2509
|
+
return [];
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
function shapeOf(parts, locales) {
|
|
2513
|
+
return parts.map((part) => locales.has(part.toLowerCase()) ? "{locale}" : part);
|
|
2514
|
+
}
|
|
2515
|
+
function patternOf(parts, varDepths) {
|
|
2516
|
+
const out = [];
|
|
2517
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
2518
|
+
const prefix = parts.slice(0, i).join("/");
|
|
2519
|
+
out.push(varDepths.has(`${i}:${prefix}`) ? `{${i}}` : parts[i]);
|
|
2520
|
+
}
|
|
2521
|
+
return `/${out.join("/")}`;
|
|
2522
|
+
}
|
|
2523
|
+
function representatives(sorted2, k) {
|
|
2524
|
+
if (sorted2.length <= k) return [...sorted2];
|
|
2525
|
+
if (k <= 1) return [sorted2[0]];
|
|
2526
|
+
const picked = [];
|
|
2527
|
+
for (let i = 0; i < k; i += 1) {
|
|
2528
|
+
picked.push(sorted2[Math.round(i * (sorted2.length - 1) / (k - 1))]);
|
|
2529
|
+
}
|
|
2530
|
+
return [...new Set(picked)];
|
|
2531
|
+
}
|
|
2532
|
+
function selectByStructure(urls, options = {}) {
|
|
2533
|
+
const perFamily = options.perFamily ?? DEFAULT_PER_FAMILY;
|
|
2534
|
+
const threshold = options.threshold ?? DEFAULT_THRESHOLD;
|
|
2535
|
+
const locales = new Set((options.locales ?? []).map((l) => l.toLowerCase()));
|
|
2536
|
+
const parsed = urls.map((url) => ({ url, parts: shapeOf(segments(url), locales) }));
|
|
2537
|
+
const childrenByPrefix = /* @__PURE__ */ new Map();
|
|
2538
|
+
for (const { parts } of parsed) {
|
|
2539
|
+
for (let i = 0; i < parts.length; i += 1) {
|
|
2540
|
+
const key = `${i}:${parts.slice(0, i).join("/")}`;
|
|
2541
|
+
const set = childrenByPrefix.get(key) ?? /* @__PURE__ */ new Set();
|
|
2542
|
+
set.add(parts[i]);
|
|
2543
|
+
childrenByPrefix.set(key, set);
|
|
2544
|
+
}
|
|
2545
|
+
}
|
|
2546
|
+
const varDepths = /* @__PURE__ */ new Set();
|
|
2547
|
+
for (const [key, children] of childrenByPrefix) {
|
|
2548
|
+
const colon = key.indexOf(":");
|
|
2549
|
+
const depth = Number(key.slice(0, colon));
|
|
2550
|
+
const prefix = key.slice(colon + 1);
|
|
2551
|
+
const isFirstSection = depth === 0 || depth === 1 && prefix === "{locale}";
|
|
2552
|
+
if (isFirstSection) continue;
|
|
2553
|
+
if (children.size >= threshold) varDepths.add(key);
|
|
2554
|
+
}
|
|
2555
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2556
|
+
for (const { url, parts } of parsed) {
|
|
2557
|
+
const pattern = patternOf(parts, varDepths);
|
|
2558
|
+
const localePart = segments(url).find((p) => locales.has(p.toLowerCase())) ?? "";
|
|
2559
|
+
const key = `${localePart}|${pattern}`;
|
|
2560
|
+
const group = groups.get(key) ?? { pattern, urls: [] };
|
|
2561
|
+
group.urls.push(url);
|
|
2562
|
+
groups.set(key, group);
|
|
2563
|
+
}
|
|
2564
|
+
const keep = /* @__PURE__ */ new Set();
|
|
2565
|
+
const families = /* @__PURE__ */ new Map();
|
|
2566
|
+
for (const { pattern, urls: groupUrls } of groups.values()) {
|
|
2567
|
+
const sorted2 = [...groupUrls].sort();
|
|
2568
|
+
const chosen = sorted2.length < threshold ? sorted2 : representatives(sorted2, perFamily);
|
|
2569
|
+
for (const url of chosen) keep.add(url);
|
|
2570
|
+
if (sorted2.length >= threshold) {
|
|
2571
|
+
const seen = families.get(pattern);
|
|
2572
|
+
families.set(pattern, {
|
|
2573
|
+
pattern,
|
|
2574
|
+
size: (seen?.size ?? 0) + sorted2.length,
|
|
2575
|
+
sampled: (seen?.sampled ?? 0) + chosen.length
|
|
2576
|
+
});
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2579
|
+
return {
|
|
2580
|
+
urls: urls.filter((url) => keep.has(url)),
|
|
2581
|
+
families: [...families.values()].sort((a, b) => b.size - a.size),
|
|
2582
|
+
total: urls.length
|
|
2583
|
+
};
|
|
2584
|
+
}
|
|
2585
|
+
|
|
2492
2586
|
// src/lib/rules/advisory.ts
|
|
2493
2587
|
function resolveEvidencePath(extraction, path) {
|
|
2494
2588
|
let current = extraction;
|
|
@@ -3402,7 +3496,11 @@ async function runAudit(inputUrl, options = {}) {
|
|
|
3402
3496
|
allowInsecureTls: options.allowInsecureTls,
|
|
3403
3497
|
crawlFallback: false
|
|
3404
3498
|
}).catch(() => void 0);
|
|
3405
|
-
const
|
|
3499
|
+
const allSitemapUrls = (discovery?.urls ?? []).map((u) => u.loc);
|
|
3500
|
+
const coverageMode = options.coverage ?? (allSitemapUrls.length > 0 ? "structural" : "all");
|
|
3501
|
+
const selection = coverageMode === "structural" && allSitemapUrls.length > 0 ? selectByStructure(allSitemapUrls, { locales: options.locales }) : void 0;
|
|
3502
|
+
const sitemapUrls = selection?.urls ?? allSitemapUrls;
|
|
3503
|
+
const effectiveMaxPages = selection ? Math.max(options.maxPages ?? maxPages, selection.urls.length + 5) : maxPages;
|
|
3406
3504
|
const robots = await probeRobots(origin, {
|
|
3407
3505
|
signal: options.signal,
|
|
3408
3506
|
timeoutMs: options.timeoutMs
|
|
@@ -3411,7 +3509,7 @@ async function runAudit(inputUrl, options = {}) {
|
|
|
3411
3509
|
entryUrl: entry,
|
|
3412
3510
|
seedUrls: sitemapUrls,
|
|
3413
3511
|
depth,
|
|
3414
|
-
maxPages,
|
|
3512
|
+
maxPages: effectiveMaxPages,
|
|
3415
3513
|
include: options.include,
|
|
3416
3514
|
exclude: options.exclude,
|
|
3417
3515
|
inspectOptions: {
|
|
@@ -3434,11 +3532,18 @@ async function runAudit(inputUrl, options = {}) {
|
|
|
3434
3532
|
const okPages = pages.filter((p) => isOkStatus(p.fetch.status));
|
|
3435
3533
|
const documents = okPages.filter(isHtmlPage);
|
|
3436
3534
|
const { kept: htmlPages, dropped: duplicatePages } = dropCanonicalDuplicates(documents);
|
|
3437
|
-
const unreachablePages =
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3535
|
+
const unreachablePages = [
|
|
3536
|
+
...pages.filter((p) => !isOkStatus(p.fetch.status)).map((p) => ({
|
|
3537
|
+
id: fingerprint("page", routeKey(p.fetch.finalUrl)),
|
|
3538
|
+
url: p.fetch.finalUrl,
|
|
3539
|
+
status: p.fetch.status
|
|
3540
|
+
})),
|
|
3541
|
+
...crawlResult.errors.map((e) => ({
|
|
3542
|
+
id: fingerprint("page", routeKey(e.url)),
|
|
3543
|
+
url: e.url,
|
|
3544
|
+
status: 0
|
|
3545
|
+
}))
|
|
3546
|
+
];
|
|
3442
3547
|
const seoIssues = [];
|
|
3443
3548
|
const conformanceRows = [];
|
|
3444
3549
|
const advisories = [];
|
|
@@ -3531,7 +3636,7 @@ async function runAudit(inputUrl, options = {}) {
|
|
|
3531
3636
|
allowInsecureTls: options.allowInsecureTls,
|
|
3532
3637
|
checkExternal,
|
|
3533
3638
|
timeoutMs: options.timeoutMs,
|
|
3534
|
-
maxPages,
|
|
3639
|
+
maxPages: effectiveMaxPages,
|
|
3535
3640
|
signal: options.signal,
|
|
3536
3641
|
onProgress: (p) => options.onProgress?.({
|
|
3537
3642
|
phase: p.phase === "scan" ? "scan" : "links",
|
|
@@ -3621,14 +3726,26 @@ async function runAudit(inputUrl, options = {}) {
|
|
|
3621
3726
|
pagesFailed: linkReport.diagnostics.pagesFailed,
|
|
3622
3727
|
truncated: crawlResult.truncated || linkReport.truncated,
|
|
3623
3728
|
warnings,
|
|
3729
|
+
// What the run actually looked at. A sampled audit that does not say so
|
|
3730
|
+
// is a partial audit wearing the face of a complete one, which is worse
|
|
3731
|
+
// than the cap it replaces.
|
|
3732
|
+
coverage: {
|
|
3733
|
+
mode: coverageMode,
|
|
3734
|
+
...selection ? {
|
|
3735
|
+
considered: selection.total,
|
|
3736
|
+
selected: selection.urls.length,
|
|
3737
|
+
families: selection.families
|
|
3738
|
+
} : {}
|
|
3739
|
+
},
|
|
3624
3740
|
...ignoredHoles > 0 ? { ignoredHoles } : {},
|
|
3625
3741
|
...duplicatePages > 0 ? { duplicatePages } : {},
|
|
3626
3742
|
...discovery ? {
|
|
3627
3743
|
sitemap: {
|
|
3628
3744
|
found: discovery.diagnostics.found,
|
|
3629
3745
|
sitemapUrl: discovery.diagnostics.sitemapUrl,
|
|
3630
|
-
urlCount:
|
|
3631
|
-
uncrawled: countUncrawled(sitemapUrls, pages)
|
|
3746
|
+
urlCount: allSitemapUrls.length,
|
|
3747
|
+
uncrawled: countUncrawled(sitemapUrls, pages),
|
|
3748
|
+
...discovery.diagnostics.unreachable ? { unreachable: discovery.diagnostics.unreachable } : {}
|
|
3632
3749
|
}
|
|
3633
3750
|
} : {}
|
|
3634
3751
|
}
|
|
@@ -3699,6 +3816,24 @@ function renderTerminal(report, options = {}) {
|
|
|
3699
3816
|
(report.profile === DEFAULT_PROFILE ? "" : `, profile ${report.profile}`)
|
|
3700
3817
|
)
|
|
3701
3818
|
);
|
|
3819
|
+
const coverage = report.diagnostics.coverage;
|
|
3820
|
+
if (coverage?.families?.length && coverage.considered && coverage.selected) {
|
|
3821
|
+
const biggest = coverage.families[0];
|
|
3822
|
+
const audited = report.diagnostics.pagesCrawled;
|
|
3823
|
+
const capped = audited < coverage.selected ? `, capped by --max-pages` : "";
|
|
3824
|
+
lines.push(
|
|
3825
|
+
c(
|
|
3826
|
+
ANSI.dim,
|
|
3827
|
+
`COVERAGE ${audited} of ${coverage.considered} pages audited${capped} \xB7 ${coverage.families.length} families sampled, largest ${biggest.sampled}/${biggest.size} ${biggest.pattern}`
|
|
3828
|
+
)
|
|
3829
|
+
);
|
|
3830
|
+
lines.push(
|
|
3831
|
+
c(
|
|
3832
|
+
ANSI.dim,
|
|
3833
|
+
` Template rules are conclusive. Copy rules \u2014 title.length, description.length \u2014 are sampled.`
|
|
3834
|
+
)
|
|
3835
|
+
);
|
|
3836
|
+
}
|
|
3702
3837
|
lines.push("");
|
|
3703
3838
|
lines.push(
|
|
3704
3839
|
[
|
|
@@ -4275,4 +4410,4 @@ export {
|
|
|
4275
4410
|
summarize,
|
|
4276
4411
|
Logger
|
|
4277
4412
|
};
|
|
4278
|
-
//# sourceMappingURL=chunk-
|
|
4413
|
+
//# sourceMappingURL=chunk-TVJVVSLV.js.map
|