equall-cli 0.1.3 → 0.1.5
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 +27 -7
- package/dist/chunk-OQXPOPP6.js +88533 -0
- package/dist/cli.js +179 -5
- package/dist/index.js +1 -1
- package/package.json +3 -1
- package/dist/chunk-5YPLWCOT.js +0 -40531
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
__toESM,
|
|
8
8
|
globby,
|
|
9
9
|
runScan
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-OQXPOPP6.js";
|
|
11
11
|
|
|
12
12
|
// node_modules/commander/lib/error.js
|
|
13
13
|
var require_error = __commonJS({
|
|
@@ -3011,6 +3011,8 @@ var require_commander = __commonJS({
|
|
|
3011
3011
|
|
|
3012
3012
|
// src/cli.ts
|
|
3013
3013
|
import { resolve as resolve2, basename } from "path";
|
|
3014
|
+
import { readFileSync, existsSync, statSync } from "fs";
|
|
3015
|
+
import { fileURLToPath } from "url";
|
|
3014
3016
|
|
|
3015
3017
|
// node_modules/commander/esm.mjs
|
|
3016
3018
|
var import_index = __toESM(require_commander(), 1);
|
|
@@ -6410,6 +6412,107 @@ function ora(options) {
|
|
|
6410
6412
|
return new Ora(options);
|
|
6411
6413
|
}
|
|
6412
6414
|
|
|
6415
|
+
// src/wcag-catalog.ts
|
|
6416
|
+
var WCAG_CATALOG = [
|
|
6417
|
+
// Principle 1 — Perceivable
|
|
6418
|
+
{ id: "1.1.1", name: "Non-text Content", level: "A", pour: "perceivable" },
|
|
6419
|
+
{ id: "1.2.1", name: "Audio-only and Video-only (Prerecorded)", level: "A", pour: "perceivable" },
|
|
6420
|
+
{ id: "1.2.2", name: "Captions (Prerecorded)", level: "A", pour: "perceivable" },
|
|
6421
|
+
{ id: "1.2.3", name: "Audio Description or Media Alternative", level: "A", pour: "perceivable" },
|
|
6422
|
+
{ id: "1.2.4", name: "Captions (Live)", level: "AA", pour: "perceivable" },
|
|
6423
|
+
{ id: "1.2.5", name: "Audio Description (Prerecorded)", level: "AA", pour: "perceivable" },
|
|
6424
|
+
{ id: "1.2.6", name: "Sign Language (Prerecorded)", level: "AAA", pour: "perceivable" },
|
|
6425
|
+
{ id: "1.2.7", name: "Extended Audio Description (Prerecorded)", level: "AAA", pour: "perceivable" },
|
|
6426
|
+
{ id: "1.2.8", name: "Media Alternative (Prerecorded)", level: "AAA", pour: "perceivable" },
|
|
6427
|
+
{ id: "1.2.9", name: "Audio-only (Live)", level: "AAA", pour: "perceivable" },
|
|
6428
|
+
{ id: "1.3.1", name: "Info and Relationships", level: "A", pour: "perceivable" },
|
|
6429
|
+
{ id: "1.3.2", name: "Meaningful Sequence", level: "A", pour: "perceivable" },
|
|
6430
|
+
{ id: "1.3.3", name: "Sensory Characteristics", level: "A", pour: "perceivable" },
|
|
6431
|
+
{ id: "1.3.4", name: "Orientation", level: "AA", pour: "perceivable" },
|
|
6432
|
+
{ id: "1.3.5", name: "Identify Input Purpose", level: "AA", pour: "perceivable" },
|
|
6433
|
+
{ id: "1.3.6", name: "Identify Purpose", level: "AAA", pour: "perceivable" },
|
|
6434
|
+
{ id: "1.4.1", name: "Use of Color", level: "A", pour: "perceivable" },
|
|
6435
|
+
{ id: "1.4.2", name: "Audio Control", level: "A", pour: "perceivable" },
|
|
6436
|
+
{ id: "1.4.3", name: "Contrast (Minimum)", level: "AA", pour: "perceivable" },
|
|
6437
|
+
{ id: "1.4.4", name: "Resize Text", level: "AA", pour: "perceivable" },
|
|
6438
|
+
{ id: "1.4.5", name: "Images of Text", level: "AA", pour: "perceivable" },
|
|
6439
|
+
{ id: "1.4.6", name: "Contrast (Enhanced)", level: "AAA", pour: "perceivable" },
|
|
6440
|
+
{ id: "1.4.7", name: "Low or No Background Audio", level: "AAA", pour: "perceivable" },
|
|
6441
|
+
{ id: "1.4.8", name: "Visual Presentation", level: "AAA", pour: "perceivable" },
|
|
6442
|
+
{ id: "1.4.9", name: "Images of Text (No Exception)", level: "AAA", pour: "perceivable" },
|
|
6443
|
+
{ id: "1.4.10", name: "Reflow", level: "AA", pour: "perceivable" },
|
|
6444
|
+
{ id: "1.4.11", name: "Non-text Contrast", level: "AA", pour: "perceivable" },
|
|
6445
|
+
{ id: "1.4.12", name: "Text Spacing", level: "AA", pour: "perceivable" },
|
|
6446
|
+
{ id: "1.4.13", name: "Content on Hover or Focus", level: "AA", pour: "perceivable" },
|
|
6447
|
+
// Principle 2 — Operable
|
|
6448
|
+
{ id: "2.1.1", name: "Keyboard", level: "A", pour: "operable" },
|
|
6449
|
+
{ id: "2.1.2", name: "No Keyboard Trap", level: "A", pour: "operable" },
|
|
6450
|
+
{ id: "2.1.3", name: "Keyboard (No Exception)", level: "AAA", pour: "operable" },
|
|
6451
|
+
{ id: "2.1.4", name: "Character Key Shortcuts", level: "A", pour: "operable" },
|
|
6452
|
+
{ id: "2.2.1", name: "Timing Adjustable", level: "A", pour: "operable" },
|
|
6453
|
+
{ id: "2.2.2", name: "Pause, Stop, Hide", level: "A", pour: "operable" },
|
|
6454
|
+
{ id: "2.2.3", name: "No Timing", level: "AAA", pour: "operable" },
|
|
6455
|
+
{ id: "2.2.4", name: "Interruptions", level: "AAA", pour: "operable" },
|
|
6456
|
+
{ id: "2.2.5", name: "Re-authenticating", level: "AAA", pour: "operable" },
|
|
6457
|
+
{ id: "2.2.6", name: "Timeouts", level: "AAA", pour: "operable" },
|
|
6458
|
+
{ id: "2.3.1", name: "Three Flashes or Below Threshold", level: "A", pour: "operable" },
|
|
6459
|
+
{ id: "2.3.2", name: "Three Flashes", level: "AAA", pour: "operable" },
|
|
6460
|
+
{ id: "2.3.3", name: "Animation from Interactions", level: "AAA", pour: "operable" },
|
|
6461
|
+
{ id: "2.4.1", name: "Bypass Blocks", level: "A", pour: "operable" },
|
|
6462
|
+
{ id: "2.4.2", name: "Page Titled", level: "A", pour: "operable" },
|
|
6463
|
+
{ id: "2.4.3", name: "Focus Order", level: "A", pour: "operable" },
|
|
6464
|
+
{ id: "2.4.4", name: "Link Purpose (In Context)", level: "A", pour: "operable" },
|
|
6465
|
+
{ id: "2.4.5", name: "Multiple Ways", level: "AA", pour: "operable" },
|
|
6466
|
+
{ id: "2.4.6", name: "Headings and Labels", level: "AA", pour: "operable" },
|
|
6467
|
+
{ id: "2.4.7", name: "Focus Visible", level: "AA", pour: "operable" },
|
|
6468
|
+
{ id: "2.4.8", name: "Location", level: "AAA", pour: "operable" },
|
|
6469
|
+
{ id: "2.4.9", name: "Link Purpose (Link Only)", level: "AAA", pour: "operable" },
|
|
6470
|
+
{ id: "2.4.10", name: "Section Headings", level: "AAA", pour: "operable" },
|
|
6471
|
+
{ id: "2.4.11", name: "Focus Not Obscured (Minimum)", level: "AA", pour: "operable" },
|
|
6472
|
+
{ id: "2.4.12", name: "Focus Not Obscured (Enhanced)", level: "AAA", pour: "operable" },
|
|
6473
|
+
{ id: "2.4.13", name: "Focus Appearance", level: "AAA", pour: "operable" },
|
|
6474
|
+
{ id: "2.5.1", name: "Pointer Gestures", level: "A", pour: "operable" },
|
|
6475
|
+
{ id: "2.5.2", name: "Pointer Cancellation", level: "A", pour: "operable" },
|
|
6476
|
+
{ id: "2.5.3", name: "Label in Name", level: "A", pour: "operable" },
|
|
6477
|
+
{ id: "2.5.4", name: "Motion Actuation", level: "A", pour: "operable" },
|
|
6478
|
+
{ id: "2.5.5", name: "Target Size (Enhanced)", level: "AAA", pour: "operable" },
|
|
6479
|
+
{ id: "2.5.6", name: "Concurrent Input Mechanisms", level: "A", pour: "operable" },
|
|
6480
|
+
{ id: "2.5.7", name: "Dragging Movements", level: "AA", pour: "operable" },
|
|
6481
|
+
{ id: "2.5.8", name: "Target Size (Minimum)", level: "AA", pour: "operable" },
|
|
6482
|
+
// Principle 3 — Understandable
|
|
6483
|
+
{ id: "3.1.1", name: "Language of Page", level: "A", pour: "understandable" },
|
|
6484
|
+
{ id: "3.1.2", name: "Language of Parts", level: "AA", pour: "understandable" },
|
|
6485
|
+
{ id: "3.1.3", name: "Unusual Words", level: "AAA", pour: "understandable" },
|
|
6486
|
+
{ id: "3.1.4", name: "Abbreviations", level: "AAA", pour: "understandable" },
|
|
6487
|
+
{ id: "3.1.5", name: "Reading Level", level: "AAA", pour: "understandable" },
|
|
6488
|
+
{ id: "3.1.6", name: "Pronunciation", level: "AAA", pour: "understandable" },
|
|
6489
|
+
{ id: "3.2.1", name: "On Focus", level: "A", pour: "understandable" },
|
|
6490
|
+
{ id: "3.2.2", name: "On Input", level: "A", pour: "understandable" },
|
|
6491
|
+
{ id: "3.2.3", name: "Consistent Navigation", level: "AA", pour: "understandable" },
|
|
6492
|
+
{ id: "3.2.4", name: "Consistent Identification", level: "AA", pour: "understandable" },
|
|
6493
|
+
{ id: "3.2.5", name: "Change on Request", level: "AAA", pour: "understandable" },
|
|
6494
|
+
{ id: "3.2.6", name: "Consistent Help", level: "A", pour: "understandable" },
|
|
6495
|
+
{ id: "3.3.1", name: "Error Identification", level: "A", pour: "understandable" },
|
|
6496
|
+
{ id: "3.3.2", name: "Labels or Instructions", level: "A", pour: "understandable" },
|
|
6497
|
+
{ id: "3.3.3", name: "Error Suggestion", level: "AA", pour: "understandable" },
|
|
6498
|
+
{ id: "3.3.4", name: "Error Prevention (Legal, Financial, Data)", level: "AA", pour: "understandable" },
|
|
6499
|
+
{ id: "3.3.5", name: "Help", level: "AAA", pour: "understandable" },
|
|
6500
|
+
{ id: "3.3.6", name: "Error Prevention (All)", level: "AAA", pour: "understandable" },
|
|
6501
|
+
{ id: "3.3.7", name: "Redundant Entry", level: "A", pour: "understandable" },
|
|
6502
|
+
{ id: "3.3.8", name: "Accessible Authentication (Minimum)", level: "AA", pour: "understandable" },
|
|
6503
|
+
{ id: "3.3.9", name: "Accessible Authentication (Enhanced)", level: "AAA", pour: "understandable" },
|
|
6504
|
+
// Principle 4 — Robust
|
|
6505
|
+
{ id: "4.1.2", name: "Name, Role, Value", level: "A", pour: "robust" },
|
|
6506
|
+
{ id: "4.1.3", name: "Status Messages", level: "AA", pour: "robust" }
|
|
6507
|
+
];
|
|
6508
|
+
var LEVEL_RANK = { A: 1, AA: 2, AAA: 3 };
|
|
6509
|
+
var catalogMap = /* @__PURE__ */ new Map();
|
|
6510
|
+
for (const c of WCAG_CATALOG) catalogMap.set(c.id, c);
|
|
6511
|
+
function getCriteriaForLevel(level) {
|
|
6512
|
+
const maxRank = LEVEL_RANK[level];
|
|
6513
|
+
return WCAG_CATALOG.filter((c) => LEVEL_RANK[c.level] <= maxRank);
|
|
6514
|
+
}
|
|
6515
|
+
|
|
6413
6516
|
// src/output/terminal.ts
|
|
6414
6517
|
var WCAG_A_CRITERIA = /* @__PURE__ */ new Set([
|
|
6415
6518
|
"1.1.1",
|
|
@@ -6435,15 +6538,17 @@ var WCAG_A_CRITERIA = /* @__PURE__ */ new Set([
|
|
|
6435
6538
|
"2.5.2",
|
|
6436
6539
|
"2.5.3",
|
|
6437
6540
|
"2.5.4",
|
|
6541
|
+
"2.5.6",
|
|
6438
6542
|
"3.1.1",
|
|
6439
6543
|
"3.2.1",
|
|
6440
6544
|
"3.2.2",
|
|
6441
6545
|
"3.2.6",
|
|
6442
6546
|
"3.3.1",
|
|
6547
|
+
"3.3.2",
|
|
6443
6548
|
"3.3.7",
|
|
6444
6549
|
"4.1.2"
|
|
6445
6550
|
]);
|
|
6446
|
-
var WCAG_A_TOTAL =
|
|
6551
|
+
var WCAG_A_TOTAL = 32;
|
|
6447
6552
|
var BP_HINTS = {
|
|
6448
6553
|
"region": "Landmarks help screen reader users navigate page sections",
|
|
6449
6554
|
"landmark-main-is-top-level": "Nested landmarks confuse assistive technology",
|
|
@@ -6599,6 +6704,22 @@ function printResult(result, options = {}) {
|
|
|
6599
6704
|
const hint = BP_HINTS[criterion];
|
|
6600
6705
|
const hintSuffix = hint ? ` ${DIM}${hint}${RESET}` : "";
|
|
6601
6706
|
console.log(` ${severityIcon(topSeverity)} ${BOLD}${criterion}${RESET} ${DIM}\u2014 ${group.issues.length} issue${group.issues.length > 1 ? "s" : ""}${RESET}${hintSuffix}`);
|
|
6707
|
+
const maxFiles = options.verbose ? group.issues.length : 2;
|
|
6708
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6709
|
+
const uniqueIssues = [];
|
|
6710
|
+
for (const issue of group.issues) {
|
|
6711
|
+
if (!seen.has(issue.file_path)) {
|
|
6712
|
+
seen.add(issue.file_path);
|
|
6713
|
+
uniqueIssues.push(issue);
|
|
6714
|
+
}
|
|
6715
|
+
}
|
|
6716
|
+
for (const issue of uniqueIssues.slice(0, maxFiles)) {
|
|
6717
|
+
const location = issue.line ? `:${issue.line}` : "";
|
|
6718
|
+
console.log(` ${DIM}${issue.file_path}${location}${RESET}`);
|
|
6719
|
+
}
|
|
6720
|
+
if (!options.verbose && uniqueIssues.length > 2) {
|
|
6721
|
+
console.log(` ${DIM}... and ${uniqueIssues.length - 2} more (use --verbose to see all)${RESET}`);
|
|
6722
|
+
}
|
|
6602
6723
|
}
|
|
6603
6724
|
console.log();
|
|
6604
6725
|
}
|
|
@@ -6613,6 +6734,20 @@ function printResult(result, options = {}) {
|
|
|
6613
6734
|
console.log();
|
|
6614
6735
|
}
|
|
6615
6736
|
}
|
|
6737
|
+
if (options.showManual) {
|
|
6738
|
+
const level = options.targetLevel ?? "AA";
|
|
6739
|
+
const allForLevel = getCriteriaForLevel(level);
|
|
6740
|
+
const coveredSet = new Set(result.criteria_covered);
|
|
6741
|
+
const untested = allForLevel.filter((c) => !coveredSet.has(c.id));
|
|
6742
|
+
if (untested.length > 0) {
|
|
6743
|
+
console.log(` ${BOLD}Manual review needed${RESET} ${DIM}(${untested.length} criteria)${RESET}`);
|
|
6744
|
+
for (const c of untested) {
|
|
6745
|
+
const principle = c.pour.charAt(0).toUpperCase() + c.pour.slice(1);
|
|
6746
|
+
console.log(` ${DIM}${c.id}${RESET} ${c.name} ${DIM}\u2014 ${principle}${RESET}`);
|
|
6747
|
+
}
|
|
6748
|
+
console.log();
|
|
6749
|
+
}
|
|
6750
|
+
}
|
|
6616
6751
|
console.log(` ${DIM}Scanners: ${scanners_used.map((s) => `${s.name}@${s.version} (${s.issues_found} issues)`).join(", ")}${RESET}`);
|
|
6617
6752
|
console.log(` ${DIM}Completed in ${(duration_ms / 1e3).toFixed(1)}s${RESET}`);
|
|
6618
6753
|
console.log();
|
|
@@ -6716,6 +6851,28 @@ async function findIgnores(rootPath) {
|
|
|
6716
6851
|
}
|
|
6717
6852
|
return entries.sort((a, b) => a.file_path.localeCompare(b.file_path) || a.line - b.line);
|
|
6718
6853
|
}
|
|
6854
|
+
async function addIgnoreFile(rootPath, filePath) {
|
|
6855
|
+
const absolutePath = resolve(rootPath, filePath);
|
|
6856
|
+
let content;
|
|
6857
|
+
try {
|
|
6858
|
+
content = await readFile(absolutePath, "utf-8");
|
|
6859
|
+
} catch {
|
|
6860
|
+
return null;
|
|
6861
|
+
}
|
|
6862
|
+
const lines = content.split("\n");
|
|
6863
|
+
if (lines.slice(0, 5).some((l) => l.includes("equall-ignore-file"))) {
|
|
6864
|
+
return null;
|
|
6865
|
+
}
|
|
6866
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
6867
|
+
let comment;
|
|
6868
|
+
if (ext === "html" || ext === "htm") {
|
|
6869
|
+
comment = "<!-- equall-ignore-file -->";
|
|
6870
|
+
} else {
|
|
6871
|
+
comment = "// equall-ignore-file";
|
|
6872
|
+
}
|
|
6873
|
+
await writeFile(absolutePath, comment + "\n" + content, "utf-8");
|
|
6874
|
+
return { file: filePath, comment };
|
|
6875
|
+
}
|
|
6719
6876
|
async function removeIgnore(rootPath, target) {
|
|
6720
6877
|
const ignores = await findIgnores(rootPath);
|
|
6721
6878
|
const colonIdx = target.lastIndexOf(":");
|
|
@@ -6809,9 +6966,11 @@ async function clearAllIgnores(rootPath) {
|
|
|
6809
6966
|
}
|
|
6810
6967
|
|
|
6811
6968
|
// src/cli.ts
|
|
6969
|
+
var __dir = resolve2(fileURLToPath(import.meta.url), "..");
|
|
6970
|
+
var pkg = JSON.parse(readFileSync(resolve2(__dir, "..", "package.json"), "utf-8"));
|
|
6812
6971
|
var program2 = new Command();
|
|
6813
|
-
program2.name("equall").description("Open-source accessibility scoring \u2014 aggregates axe-core, eslint-plugin-jsx-a11y, and more.").version(
|
|
6814
|
-
program2.command("scan").description("Scan a project for accessibility issues").argument("[path]", "Path to project root", ".").option("-l, --level <level>", "WCAG conformance target: A, AA, or AAA", "AA").option("--include <patterns...>", "Glob patterns to include").option("--exclude <patterns...>", "Glob patterns to exclude").option("--json", "Output results as JSON").option("-i, --show-ignored", "Show ignored issues in output").option("--no-color", "Disable colored output").action(async (path, opts) => {
|
|
6972
|
+
program2.name("equall").description("Open-source accessibility scoring \u2014 aggregates axe-core, eslint-plugin-jsx-a11y, and more.").version(pkg.version);
|
|
6973
|
+
program2.command("scan").description("Scan a project for accessibility issues").argument("[path]", "Path to project root", ".").option("-l, --level <level>", "WCAG conformance target: A, AA, or AAA", "AA").option("--include <patterns...>", "Glob patterns to include").option("--exclude <patterns...>", "Glob patterns to exclude").option("--json", "Output results as JSON").option("-i, --show-ignored", "Show ignored issues in output").option("-v, --verbose", "Show all occurrences for best-practice issues").option("-m, --show-manual", "List WCAG criteria that require manual review").option("--no-color", "Disable colored output").action(async (path, opts) => {
|
|
6815
6974
|
const level = opts.level.toUpperCase();
|
|
6816
6975
|
if (!["A", "AA", "AAA"].includes(level)) {
|
|
6817
6976
|
console.error(`Invalid level "${opts.level}". Use A, AA, or AAA.`);
|
|
@@ -6839,7 +6998,7 @@ program2.command("scan").description("Scan a project for accessibility issues").
|
|
|
6839
6998
|
if (opts.json) {
|
|
6840
6999
|
printJson(result);
|
|
6841
7000
|
} else {
|
|
6842
|
-
printResult(result, { showIgnored: opts.showIgnored });
|
|
7001
|
+
printResult(result, { showIgnored: opts.showIgnored, verbose: opts.verbose, showManual: opts.showManual, targetLevel: level });
|
|
6843
7002
|
}
|
|
6844
7003
|
if (result.score < 50) process.exit(1);
|
|
6845
7004
|
} catch (error2) {
|
|
@@ -6895,6 +7054,21 @@ program2.command("ignore").description("Add, list, or remove equall-ignore comme
|
|
|
6895
7054
|
console.log(`
|
|
6896
7055
|
${GREEN2}Added${RESET2} ${result.file}:${result.line}`);
|
|
6897
7056
|
console.log(` ${DIM2}${result.comment.trim()}${RESET2}
|
|
7057
|
+
`);
|
|
7058
|
+
return;
|
|
7059
|
+
}
|
|
7060
|
+
const isDirectory = target && existsSync(resolve2(rootPath, target)) && statSync(resolve2(rootPath, target)).isDirectory();
|
|
7061
|
+
if (target && !isDirectory && (target.includes("/") || target.match(/\.\w+$/))) {
|
|
7062
|
+
const result = await addIgnoreFile(rootPath, target);
|
|
7063
|
+
if (!result) {
|
|
7064
|
+
console.error(`
|
|
7065
|
+
Could not ignore ${target}. File not found or already ignored.
|
|
7066
|
+
`);
|
|
7067
|
+
process.exit(1);
|
|
7068
|
+
}
|
|
7069
|
+
console.log(`
|
|
7070
|
+
${GREEN2}Added${RESET2} ${result.file}`);
|
|
7071
|
+
console.log(` ${DIM2}${result.comment}${RESET2}
|
|
6898
7072
|
`);
|
|
6899
7073
|
return;
|
|
6900
7074
|
}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "equall-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Open-source accessibility scoring CLI — aggregates axe-core, eslint-plugin-jsx-a11y, and more into a unified score.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|
|
@@ -51,12 +51,14 @@
|
|
|
51
51
|
"@types/jsdom": "^21.1.7",
|
|
52
52
|
"@types/node": "^22.0.0",
|
|
53
53
|
"axe-core": "^4.10.0",
|
|
54
|
+
"cheerio": "^1.2.0",
|
|
54
55
|
"commander": "^12.1.0",
|
|
55
56
|
"eslint": "^9.0.0",
|
|
56
57
|
"eslint-plugin-jsx-a11y": "^6.10.0",
|
|
57
58
|
"globby": "^14.0.0",
|
|
58
59
|
"jsdom": "^25.0.0",
|
|
59
60
|
"ora": "^9.3.0",
|
|
61
|
+
"text-readability": "^1.1.1",
|
|
60
62
|
"tsup": "^8.0.0",
|
|
61
63
|
"tsx": "^4.0.0",
|
|
62
64
|
"typescript": "^5.9.3",
|