@hawkeyexl/manni 2.7.1 → 2.8.1

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/cli.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  readStdin,
8
8
  reportConfig,
9
9
  splitList
10
- } from "./chunk-QVRRMH2T.js";
10
+ } from "./chunk-IQO55BTU.js";
11
11
  import {
12
12
  CiteError,
13
13
  KeyError,
@@ -40,9 +40,10 @@ import {
40
40
  shortPin,
41
41
  shortSrc,
42
42
  spellSource
43
- } from "./chunk-3WGFQKOP.js";
43
+ } from "./chunk-FWB6LGBB.js";
44
44
  import {
45
45
  COMMON_FORMAT_LIST,
46
+ FAMILY_CONFIG_NAMES,
46
47
  OMITTED_WHEN_CLEAN,
47
48
  REPORT_FORMATS,
48
49
  REPORT_FORMAT_LIST,
@@ -62,6 +63,7 @@ import {
62
63
  isMachineFormat,
63
64
  isReportFormat,
64
65
  locateFrontmatter,
66
+ matchesFileGlob,
65
67
  notice,
66
68
  package_default,
67
69
  palette,
@@ -78,7 +80,7 @@ import {
78
80
  splitPageArgument,
79
81
  terminalConfirm,
80
82
  valeConfigPath
81
- } from "./chunk-PRCLNOMG.js";
83
+ } from "./chunk-RLHN765L.js";
82
84
 
83
85
  // src/cli.ts
84
86
  import { Command as Command5 } from "commander";
@@ -172,6 +174,7 @@ async function crawl(opts, analyzer) {
172
174
  const frontier = [];
173
175
  const seen = /* @__PURE__ */ new Set();
174
176
  const visited = /* @__PURE__ */ new Set();
177
+ const excluded = /* @__PURE__ */ new Set();
175
178
  const progress = opts.onProgress ?? (() => void 0);
176
179
  const enqueue = (raw, source) => {
177
180
  let url;
@@ -184,6 +187,10 @@ async function crawl(opts, analyzer) {
184
187
  }
185
188
  if (source !== "seed" && !isPageLink(url)) return;
186
189
  if (!opts.seeds.some((seed) => sameHost(seed, url))) return;
190
+ if (opts.exclude?.matches(url) === true) {
191
+ excluded.add(key);
192
+ return;
193
+ }
187
194
  if (seen.has(key)) return;
188
195
  seen.add(key);
189
196
  frontier.push({ url, key, source });
@@ -199,6 +206,9 @@ async function crawl(opts, analyzer) {
199
206
  };
200
207
  for (const seed of opts.seeds) enqueue(seed, "seed");
201
208
  if (opts.crawl) for (const url of opts.extra) enqueue(url, "sitemap");
209
+ if (excluded.size > 0) {
210
+ progress({ kind: "excluded", urls: excluded.size, patterns: opts.exclude?.patterns.length ?? 0 });
211
+ }
202
212
  const pages = [];
203
213
  let duplicates = 0;
204
214
  const capped = () => opts.maxPages !== void 0 && pages.length >= opts.maxPages;
@@ -229,7 +239,37 @@ async function crawl(opts, analyzer) {
229
239
  }
230
240
  const skipped2 = frontier.length - pages.length - duplicates;
231
241
  progress({ kind: "done", checked: pages.length, skipped: skipped2 });
232
- return { pages, discovered: frontier.length, skipped: skipped2, duplicates };
242
+ return { pages, discovered: frontier.length, skipped: skipped2, duplicates, excluded: excluded.size };
243
+ }
244
+
245
+ // src/a11y/core/exclude.ts
246
+ var MUST_START_WITH_SLASH = 'must start with "/": it matches a URL path.';
247
+ function isUrlPathGlob(pattern) {
248
+ return pattern.startsWith("/");
249
+ }
250
+ function excludePath(url) {
251
+ let parsed;
252
+ try {
253
+ parsed = new URL(url);
254
+ } catch {
255
+ return null;
256
+ }
257
+ const path = parsed.pathname;
258
+ return path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
259
+ }
260
+ function createExcludeFilter(patterns) {
261
+ const list = [...patterns];
262
+ const match = (url) => {
263
+ if (list.length === 0) return null;
264
+ const path = excludePath(url);
265
+ if (path === null) return null;
266
+ return list.find((pattern) => matchesFileGlob(path, pattern.glob)) ?? null;
267
+ };
268
+ return {
269
+ patterns: list,
270
+ match,
271
+ matches: (url) => match(url) !== null
272
+ };
233
273
  }
234
274
 
235
275
  // src/a11y/core/seeds.ts
@@ -401,7 +441,8 @@ var CHECK_DEFAULTS = Object.freeze({
401
441
  crawl: true,
402
442
  tags: [],
403
443
  severity: "notice",
404
- timeout: 3e4
444
+ timeout: 3e4,
445
+ exclude: []
405
446
  });
406
447
  async function runCheck2(opts, deps) {
407
448
  const { analyzer } = deps;
@@ -413,6 +454,12 @@ async function runCheck2(opts, deps) {
413
454
  if (!isHttpUrl(url)) throw new A11yError(`Not an http(s) URL: "${url}".`);
414
455
  }
415
456
  const seeds = opts.urls.map(normalizeUrl);
457
+ const exclude = createExcludeFilter(opts.exclude);
458
+ const contradictions = seeds.map((seed) => {
459
+ const pattern = exclude.match(seed);
460
+ return pattern === null ? null : `${pattern.source} excludes the seed ${seed}.`;
461
+ }).filter((line) => line !== null);
462
+ if (contradictions.length > 0) throw new A11yError(contradictions.join("\n"));
416
463
  let sitemap = { source: null, urls: [] };
417
464
  const first = seeds[0];
418
465
  if (opts.crawl && first !== void 0) {
@@ -425,6 +472,7 @@ async function runCheck2(opts, deps) {
425
472
  crawl: opts.crawl,
426
473
  maxPages: opts.maxPages,
427
474
  extra: sitemap.urls,
475
+ exclude,
428
476
  analyze: { tags: opts.tags, timeout: opts.timeout },
429
477
  onProgress: deps.onProgress
430
478
  },
@@ -446,7 +494,7 @@ function finishPage(page, floor) {
446
494
  const score = applicable === 0 ? null : Math.round(100 * page.passes / applicable);
447
495
  return { ...page, violations, score };
448
496
  }
449
- function summarize(results, { discovered, skipped: skipped2, duplicates }, sitemap, crawl2) {
497
+ function summarize(results, { discovered, skipped: skipped2, duplicates, excluded }, sitemap, crawl2) {
450
498
  const bySeverity = { notice: 0, warning: 0, error: 0 };
451
499
  let failed = 0;
452
500
  let violations = 0;
@@ -462,6 +510,7 @@ function summarize(results, { discovered, skipped: skipped2, duplicates }, sitem
462
510
  checked: results.length,
463
511
  skipped: skipped2,
464
512
  duplicates,
513
+ excluded,
465
514
  failed,
466
515
  violations,
467
516
  bySeverity,
@@ -585,7 +634,7 @@ function reason(err) {
585
634
 
586
635
  // src/a11y/core/config.ts
587
636
  var SECTION = "a11y";
588
- var CONFIG_KEYS = ["urls", "crawl", "maxPages", "tags", "severity", "timeout"];
637
+ var CONFIG_KEYS = ["urls", "crawl", "maxPages", "tags", "severity", "timeout", "exclude"];
589
638
  var CONFIG_FILE = {
590
639
  section: SECTION,
591
640
  // The a11y tool never had a pre-family file of its own.
@@ -625,8 +674,21 @@ function parseA11yConfig(value, source) {
625
674
  if (obj.timeout !== void 0) {
626
675
  config.timeout = asPositiveInteger(obj.timeout, "timeout", source);
627
676
  }
677
+ if (obj.exclude !== void 0) config.exclude = asExcludes(obj.exclude, source);
628
678
  return config;
629
679
  }
680
+ function excludeEntryLabel(source, index) {
681
+ return `${source}: "${SECTION}.exclude[${String(index)}]"`;
682
+ }
683
+ function asExcludes(value, source) {
684
+ const globs = asStringList(value, "exclude", source);
685
+ globs.forEach((glob, i) => {
686
+ const at = excludeEntryLabel(source, i);
687
+ if (glob === "") throw new A11yError(`${at} must be a non-empty string.`);
688
+ if (!isUrlPathGlob(glob)) throw new A11yError(`${at} ${MUST_START_WITH_SLASH}`);
689
+ });
690
+ return globs;
691
+ }
630
692
  function asStringList(value, field, source) {
631
693
  if (!Array.isArray(value) || !value.every((v) => typeof v === "string")) {
632
694
  throw new A11yError(`${source}: "${SECTION}.${field}" must be a list of strings.`);
@@ -717,7 +779,7 @@ function renderPretty(run, opts) {
717
779
  for (const v of page.violations) lines.push(...violationLines(v, c));
718
780
  }
719
781
  const failed = summary.failed;
720
- const footer = `${summary.violations} violation${summary.violations === 1 ? "" : "s"} on ${failed} of ${summary.checked} pages` + (summary.skipped > 0 ? `; ${summary.skipped} skipped (--max-pages)` : "") + (summary.duplicates > 0 ? `; ${summary.duplicates} duplicate${summary.duplicates === 1 ? "" : "s"} dropped` : "");
782
+ const footer = `${summary.violations} violation${summary.violations === 1 ? "" : "s"} on ${failed} of ${summary.checked} pages` + (summary.skipped > 0 ? `; ${summary.skipped} skipped (--max-pages)` : "") + (summary.duplicates > 0 ? `; ${summary.duplicates} duplicate${summary.duplicates === 1 ? "" : "s"} dropped` : "") + (summary.excluded > 0 ? `; ${summary.excluded} excluded` : "");
721
783
  lines.push("");
722
784
  lines.push(failed > 0 ? c.red(footer) : c.green(footer));
723
785
  return lines.join("\n");
@@ -826,6 +888,9 @@ function terminalReporter(opts) {
826
888
  if (event.source === null) show("No sitemap; following links");
827
889
  else showUrl("Sitemap: ", event.source, ` (${plural(event.urls, "page")})`);
828
890
  return;
891
+ case "excluded":
892
+ show(`Excluded ${plural(event.urls, "page")} (${plural(event.patterns, "pattern")})`);
893
+ return;
829
894
  case "page":
830
895
  queued = event.queued;
831
896
  showUrl(`${c.dim(counter(event.index, queued))} `, event.url);
@@ -859,6 +924,9 @@ function plainReporter(stream) {
859
924
  if (event.source === null) say("no sitemap; following links");
860
925
  else say(`sitemap ${event.source} (${plural(event.urls, "page")})`);
861
926
  return;
927
+ case "excluded":
928
+ say(`excluded ${plural(event.urls, "page")} (${plural(event.patterns, "pattern")})`);
929
+ return;
862
930
  case "page":
863
931
  queued = event.queued;
864
932
  say(`${counter(event.index, queued)} ${event.url}`);
@@ -933,6 +1001,18 @@ function resolveMaxPages(flag, configured) {
933
1001
  function resolveCrawl(flag, configured) {
934
1002
  return flag ?? configured ?? CHECK_DEFAULTS.crawl;
935
1003
  }
1004
+ function assertExcludeGlobs(globs) {
1005
+ return globs.map((glob) => {
1006
+ if (!isUrlPathGlob(glob)) {
1007
+ throw new A11yError(`--exclude "${glob}" ${MUST_START_WITH_SLASH}`);
1008
+ }
1009
+ return { glob, source: `--exclude "${glob}"` };
1010
+ });
1011
+ }
1012
+ function configExcludeGlobs(globs, source) {
1013
+ const file = source ?? FAMILY_CONFIG_NAMES[0] ?? "manni.config.yaml";
1014
+ return globs.map((glob, i) => ({ glob, source: excludeEntryLabel(file, i) }));
1015
+ }
936
1016
  function assertSeverity(value) {
937
1017
  if (!isSeverity(value)) {
938
1018
  throw new A11yError(`Unknown --severity "${value}". Use ${SEVERITY_LIST}.`);
@@ -948,7 +1028,12 @@ function buildProgram2() {
948
1028
  ).option("-f, --format <format>", `output: ${A11Y_FORMAT_LIST}`, "pretty").option("--crawl", "crawl from the given URLs: sitemap and link following (default)").option("--no-crawl", "check exactly the given URLs: no sitemap, no link following").option(
949
1029
  "--max-pages <n>",
950
1030
  "cap on pages checked; the rest are reported as skipped (default: no cap)"
951
- ).option("--no-max-pages", "check every page found, ignoring a configured maxPages").option("--tags <list>", "comma-separated axe tags to restrict the rules to").option(
1031
+ ).option("--no-max-pages", "check every page found, ignoring a configured maxPages").option(
1032
+ "--exclude <glob>",
1033
+ "URL path glob to keep out of the crawl; repeatable",
1034
+ collect2,
1035
+ []
1036
+ ).option("--tags <list>", "comma-separated axe tags to restrict the rules to").option(
952
1037
  "--severity <level>",
953
1038
  `minimum severity reported: ${SEVERITY_LIST}`,
954
1039
  CHECK_DEFAULTS.severity
@@ -972,6 +1057,7 @@ function buildProgram2() {
972
1057
  const severity = assertSeverity(options.severity);
973
1058
  const maxPages = parseMaxPages(options.maxPages);
974
1059
  const timeout = positiveInteger(options.timeout, "--timeout");
1060
+ const exclude = assertExcludeGlobs(options.exclude);
975
1061
  const loaded = options.config === false ? { config: {}, source: null, collections: [] } : await loadA11yConfig(process.cwd(), options.config);
976
1062
  const cfg = loaded.config;
977
1063
  const seeds = resolveSeeds({
@@ -992,6 +1078,11 @@ function buildProgram2() {
992
1078
  // to, and `--no-max-pages` is the typed way to ask for none.
993
1079
  maxPages: resolveMaxPages(maxPages, cfg.maxPages),
994
1080
  tags: options.tags !== void 0 ? splitList2(options.tags) : cfg.tags ?? CHECK_DEFAULTS.tags,
1081
+ // A typed flag replaces the config list entirely; the two do not
1082
+ // merge. Merging would only ever let a run exclude *more* than
1083
+ // the repository's default, so checking one excluded section on
1084
+ // purpose would need --no-config, which also drops collections:.
1085
+ exclude: typed("exclude") ? exclude : configExcludeGlobs(cfg.exclude ?? [], loaded.source),
995
1086
  severity: typed("severity") ? severity : cfg.severity ?? CHECK_DEFAULTS.severity,
996
1087
  timeout: typed("timeout") ? timeout : cfg.timeout ?? CHECK_DEFAULTS.timeout
997
1088
  },