acpilot 1.0.0 → 1.1.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.
- package/dist/cli.js +257 -258
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -44061,12 +44061,81 @@ var undici = __toESM(require_undici(), 1);
|
|
|
44061
44061
|
var import_whatwg_mimetype = __toESM(require_mime_type(), 1);
|
|
44062
44062
|
|
|
44063
44063
|
// src/cli.ts
|
|
44064
|
+
var readline = __toESM(require("readline"));
|
|
44065
|
+
var RESET = "\x1B[0m";
|
|
44066
|
+
var BOLD = "\x1B[1m";
|
|
44067
|
+
var DIM = "\x1B[2m";
|
|
44068
|
+
var ITALIC = "\x1B[3m";
|
|
44069
|
+
var GREEN = "\x1B[32m";
|
|
44070
|
+
var RED = "\x1B[31m";
|
|
44071
|
+
var YELLOW = "\x1B[33m";
|
|
44072
|
+
var CYAN = "\x1B[36m";
|
|
44073
|
+
var BLUE = "\x1B[34m";
|
|
44074
|
+
var WHITE = "\x1B[37m";
|
|
44075
|
+
var GRAY = "\x1B[90m";
|
|
44076
|
+
var BRIGHT_WHITE = "\x1B[97m";
|
|
44077
|
+
function scoreColor(score) {
|
|
44078
|
+
if (score >= 90) return GREEN;
|
|
44079
|
+
if (score >= 70) return YELLOW;
|
|
44080
|
+
return RED;
|
|
44081
|
+
}
|
|
44082
|
+
function printLogo() {
|
|
44083
|
+
console.log("");
|
|
44084
|
+
console.log(`${BLUE} \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557${RESET}`);
|
|
44085
|
+
console.log(`${BLUE} \u2551${RESET} ${BLUE}\u2551${RESET}`);
|
|
44086
|
+
console.log(`${BLUE} \u2551${RESET} ${BOLD}${CYAN} \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 ${WHITE}\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 ${CYAN} ${RESET}${BLUE}\u2551${RESET}`);
|
|
44087
|
+
console.log(`${BLUE} \u2551${RESET} ${BOLD}${CYAN}\u2588\u2588 \u2588\u2588 \u2588\u2588 ${WHITE}\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ${CYAN} ${RESET}${BLUE}\u2551${RESET}`);
|
|
44088
|
+
console.log(`${BLUE} \u2551${RESET} ${BOLD}${CYAN}\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 ${WHITE}\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ${CYAN} ${RESET}${BLUE}\u2551${RESET}`);
|
|
44089
|
+
console.log(`${BLUE} \u2551${RESET} ${BOLD}${CYAN}\u2588\u2588 \u2588\u2588 \u2588\u2588 ${WHITE}\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ${CYAN} ${RESET}${BLUE}\u2551${RESET}`);
|
|
44090
|
+
console.log(`${BLUE} \u2551${RESET} ${BOLD}${CYAN}\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588${WHITE}\u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 ${CYAN} ${RESET}${BLUE}\u2551${RESET}`);
|
|
44091
|
+
console.log(`${BLUE} \u2551${RESET} ${BLUE}\u2551${RESET}`);
|
|
44092
|
+
console.log(`${BLUE} \u2551${RESET} ${DIM}${ITALIC} Barrierefreiheit f\xFCr Entwickler${RESET} ${DIM}v1.0.1${RESET} ${BLUE}\u2551${RESET}`);
|
|
44093
|
+
console.log(`${BLUE} \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${RESET}`);
|
|
44094
|
+
console.log("");
|
|
44095
|
+
}
|
|
44096
|
+
function printDivider(label) {
|
|
44097
|
+
if (label) {
|
|
44098
|
+
console.log(`
|
|
44099
|
+
${GRAY}\u2500\u2500${RESET} ${BOLD}${label}${RESET} ${GRAY}${"\u2500".repeat(Math.max(0, 42 - label.length))}${RESET}
|
|
44100
|
+
`);
|
|
44101
|
+
} else {
|
|
44102
|
+
console.log(` ${GRAY}${"\u2500".repeat(48)}${RESET}`);
|
|
44103
|
+
}
|
|
44104
|
+
}
|
|
44105
|
+
function createRL() {
|
|
44106
|
+
return readline.createInterface({
|
|
44107
|
+
input: process.stdin,
|
|
44108
|
+
output: process.stdout
|
|
44109
|
+
});
|
|
44110
|
+
}
|
|
44111
|
+
function ask(rl, question, defaultValue) {
|
|
44112
|
+
const suffix = defaultValue ? ` ${DIM}(${defaultValue})${RESET}` : "";
|
|
44113
|
+
return new Promise((resolve) => {
|
|
44114
|
+
rl.question(` ${CYAN}\u203A${RESET} ${question}${suffix}: `, (answer) => {
|
|
44115
|
+
resolve(answer.trim() || defaultValue || "");
|
|
44116
|
+
});
|
|
44117
|
+
});
|
|
44118
|
+
}
|
|
44119
|
+
function askChoice(rl, question, choices) {
|
|
44120
|
+
console.log(` ${CYAN}\u203A${RESET} ${question}
|
|
44121
|
+
`);
|
|
44122
|
+
for (const c of choices) {
|
|
44123
|
+
console.log(` ${BOLD}${CYAN}${c.key}${RESET} ${c.label} ${DIM}${c.desc}${RESET}`);
|
|
44124
|
+
}
|
|
44125
|
+
console.log("");
|
|
44126
|
+
return new Promise((resolve) => {
|
|
44127
|
+
rl.question(` ${CYAN}\u203A${RESET} Auswahl: `, (answer) => {
|
|
44128
|
+
resolve(answer.trim() || choices[0].key);
|
|
44129
|
+
});
|
|
44130
|
+
});
|
|
44131
|
+
}
|
|
44064
44132
|
function parseArgs() {
|
|
44065
44133
|
const args = process.argv.slice(2);
|
|
44066
44134
|
const opts = {};
|
|
44067
44135
|
const flags = /* @__PURE__ */ new Set();
|
|
44068
44136
|
for (let i = 0; i < args.length; i++) {
|
|
44069
44137
|
if (args[i] === "--help" || args[i] === "-h") {
|
|
44138
|
+
printLogo();
|
|
44070
44139
|
printHelp();
|
|
44071
44140
|
process.exit(0);
|
|
44072
44141
|
}
|
|
@@ -44078,47 +44147,36 @@ function parseArgs() {
|
|
|
44078
44147
|
opts[args[i]] = args[++i];
|
|
44079
44148
|
}
|
|
44080
44149
|
}
|
|
44081
|
-
const url = opts["--url"];
|
|
44150
|
+
const url = opts["--url"] || "";
|
|
44082
44151
|
const apiKey = opts["--api-key"] || process.env.ACCESSPILOT_API_KEY || "";
|
|
44083
|
-
|
|
44084
|
-
console.error("Fehler: --url ist erforderlich\n");
|
|
44085
|
-
printHelp();
|
|
44086
|
-
process.exit(1);
|
|
44087
|
-
}
|
|
44088
|
-
if (!apiKey) {
|
|
44089
|
-
console.error("Fehler: --api-key ist erforderlich (oder ACCESSPILOT_API_KEY Umgebungsvariable)\n");
|
|
44090
|
-
printHelp();
|
|
44091
|
-
process.exit(1);
|
|
44092
|
-
}
|
|
44152
|
+
const interactive = !url;
|
|
44093
44153
|
return {
|
|
44094
44154
|
url,
|
|
44095
44155
|
apiKey,
|
|
44096
44156
|
maxPages: parseInt(opts["--max-pages"] || "50", 10),
|
|
44097
44157
|
maxDepth: parseInt(opts["--max-depth"] || "3", 10),
|
|
44098
|
-
server: opts["--server"] || "https://
|
|
44158
|
+
server: opts["--server"] || "https://acpilot.de",
|
|
44099
44159
|
projectId: opts["--project"],
|
|
44100
44160
|
json: flags.has("--json"),
|
|
44101
|
-
noCrawl: flags.has("--no-crawl")
|
|
44161
|
+
noCrawl: flags.has("--no-crawl"),
|
|
44162
|
+
interactive
|
|
44102
44163
|
};
|
|
44103
44164
|
}
|
|
44104
44165
|
function printHelp() {
|
|
44105
|
-
console.log(`
|
|
44106
|
-
acpilot
|
|
44107
|
-
|
|
44108
|
-
|
|
44109
|
-
|
|
44110
|
-
|
|
44111
|
-
|
|
44112
|
-
--
|
|
44113
|
-
--
|
|
44114
|
-
--max-
|
|
44115
|
-
--
|
|
44116
|
-
--
|
|
44117
|
-
--
|
|
44118
|
-
|
|
44119
|
-
--json JSON-Ausgabe statt Pretty-Print
|
|
44120
|
-
-h, --help Hilfe anzeigen
|
|
44121
|
-
`);
|
|
44166
|
+
console.log(` ${BOLD}Verwendung:${RESET}`);
|
|
44167
|
+
console.log(` ${GREEN}npx acpilot${RESET} ${DIM}Interaktiver Modus${RESET}`);
|
|
44168
|
+
console.log(` ${GREEN}npx acpilot${RESET} --url http://localhost:3000 ${DIM}Direkter Modus${RESET}`);
|
|
44169
|
+
console.log(` --api-key AP_xxx`);
|
|
44170
|
+
console.log("");
|
|
44171
|
+
console.log(` ${BOLD}Optionen:${RESET}`);
|
|
44172
|
+
console.log(` ${CYAN}--url${RESET} <url> URL des Dev-Servers`);
|
|
44173
|
+
console.log(` ${CYAN}--api-key${RESET} <key> API-Key ${DIM}(oder ACCESSPILOT_API_KEY env)${RESET}`);
|
|
44174
|
+
console.log(` ${CYAN}--max-pages${RESET} <n> Max. Seiten ${DIM}(Standard: 50)${RESET}`);
|
|
44175
|
+
console.log(` ${CYAN}--max-depth${RESET} <n> Crawl-Tiefe ${DIM}(Standard: 3)${RESET}`);
|
|
44176
|
+
console.log(` ${CYAN}--project${RESET} <id> Projekt-ID ${DIM}(optional)${RESET}`);
|
|
44177
|
+
console.log(` ${CYAN}--no-crawl${RESET} Nur eine URL scannen`);
|
|
44178
|
+
console.log(` ${CYAN}--json${RESET} JSON-Ausgabe`);
|
|
44179
|
+
console.log("");
|
|
44122
44180
|
}
|
|
44123
44181
|
var SEVERITY_PENALTIES = { critical: 10, serious: 5, moderate: 2, minor: 1 };
|
|
44124
44182
|
function calculateScore(findings) {
|
|
@@ -44164,155 +44222,64 @@ function runRules(html3, url, $2) {
|
|
|
44164
44222
|
$2("img").each((_, el) => {
|
|
44165
44223
|
const alt = $2(el).attr("alt");
|
|
44166
44224
|
if (alt === void 0) {
|
|
44167
|
-
addFinding("image-alt", "critical", "Bild ohne alt-Attribut", $2.html(el) ?? "",
|
|
44225
|
+
addFinding("image-alt", "critical", "Bild ohne alt-Attribut", $2.html(el) ?? "", "img", "1.1.1", "F\xFCgen Sie ein alt-Attribut hinzu");
|
|
44168
44226
|
} else if (alt === "" && !$2(el).attr("role") && !$2(el).closest("a, button").length) {
|
|
44169
44227
|
addFinding("image-alt-empty", "moderate", "Bild mit leerem alt-Attribut (nicht dekorativ markiert)", $2.html(el) ?? "", "img", "1.1.1", 'Markieren Sie das Bild mit role="presentation" oder f\xFCgen Sie einen alt-Text hinzu');
|
|
44170
44228
|
}
|
|
44171
44229
|
});
|
|
44172
|
-
|
|
44173
|
-
if (!htmlLang) {
|
|
44230
|
+
if (!$2("html").attr("lang")) {
|
|
44174
44231
|
addFinding("html-lang", "serious", "HTML-Element hat kein lang-Attribut", "<html>", "html", "3.1.1", 'F\xFCgen Sie lang="de" zum <html>-Element hinzu');
|
|
44175
44232
|
}
|
|
44176
|
-
|
|
44177
|
-
|
|
44178
|
-
addFinding("page-title", "serious", "Seite hat keinen Titel (<title>)", "<head>", "title", "2.4.2", "F\xFCgen Sie einen aussagekr\xE4ftigen <title> hinzu");
|
|
44233
|
+
if (!$2("title").text().trim()) {
|
|
44234
|
+
addFinding("page-title", "serious", "Seite hat keinen Titel", "<head>", "title", "2.4.2", "F\xFCgen Sie einen aussagekr\xE4ftigen <title> hinzu");
|
|
44179
44235
|
}
|
|
44180
|
-
const
|
|
44236
|
+
const levels = [];
|
|
44181
44237
|
$2("h1, h2, h3, h4, h5, h6").each((_, el) => {
|
|
44182
|
-
|
|
44183
|
-
headingLevels.push(parseInt(tag.replace("h", ""), 10));
|
|
44238
|
+
levels.push(parseInt($2(el).prop("tagName")?.toLowerCase().replace("h", "") ?? "0", 10));
|
|
44184
44239
|
});
|
|
44185
|
-
if (
|
|
44186
|
-
addFinding("heading-missing", "serious", "Keine \xDCberschriften gefunden", "", "", "1.3.1", "Verwenden Sie
|
|
44240
|
+
if (levels.length === 0) {
|
|
44241
|
+
addFinding("heading-missing", "serious", "Keine \xDCberschriften gefunden", "", "", "1.3.1", "Verwenden Sie h1-h6 \xDCberschriften");
|
|
44187
44242
|
} else {
|
|
44188
|
-
if (
|
|
44189
|
-
|
|
44190
|
-
|
|
44191
|
-
|
|
44192
|
-
if (headingLevels[i] > headingLevels[i - 1] + 1) {
|
|
44193
|
-
addFinding("heading-skip", "moderate", `\xDCberschriftenebene \xFCbersprungen: h${headingLevels[i - 1]} \u2192 h${headingLevels[i]}`, "", "", "1.3.1", "\xDCberspringen Sie keine \xDCberschriftenebenen");
|
|
44243
|
+
if (levels[0] !== 1) addFinding("heading-order", "moderate", `Erste \xDCberschrift ist h${levels[0]} statt h1`, "", "", "1.3.1", "Beginnen Sie mit h1");
|
|
44244
|
+
for (let i = 1; i < levels.length; i++) {
|
|
44245
|
+
if (levels[i] > levels[i - 1] + 1) {
|
|
44246
|
+
addFinding("heading-skip", "moderate", `\xDCberschriftenebene \xFCbersprungen: h${levels[i - 1]} \u2192 h${levels[i]}`, "", "", "1.3.1", "Keine Ebenen \xFCberspringen");
|
|
44194
44247
|
break;
|
|
44195
44248
|
}
|
|
44196
44249
|
}
|
|
44197
44250
|
}
|
|
44198
44251
|
$2("a").each((_, el) => {
|
|
44199
44252
|
const text3 = $2(el).text().trim();
|
|
44200
|
-
|
|
44201
|
-
|
|
44202
|
-
const img = $2(el).find("img[alt]");
|
|
44203
|
-
if (!text3 && !ariaLabel && !title2 && img.length === 0) {
|
|
44204
|
-
addFinding("link-text", "serious", "Link ohne erkennbaren Text", $2.html(el)?.slice(0, 200) ?? "", "a", "2.4.4", "F\xFCgen Sie einen Link-Text oder aria-label hinzu");
|
|
44253
|
+
if (!text3 && !$2(el).attr("aria-label")?.trim() && !$2(el).attr("title")?.trim() && !$2(el).find("img[alt]").length) {
|
|
44254
|
+
addFinding("link-text", "serious", "Link ohne erkennbaren Text", $2.html(el)?.slice(0, 200) ?? "", "a", "2.4.4", "Link-Text oder aria-label hinzuf\xFCgen");
|
|
44205
44255
|
}
|
|
44206
44256
|
});
|
|
44207
44257
|
$2("input, select, textarea").each((_, el) => {
|
|
44208
44258
|
const type = $2(el).attr("type") || "text";
|
|
44209
44259
|
if (["hidden", "submit", "button", "reset", "image"].includes(type)) return;
|
|
44210
44260
|
const id = $2(el).attr("id");
|
|
44211
|
-
const ariaLabel = $2(el).attr("aria-label");
|
|
44212
|
-
const ariaLabelledBy = $2(el).attr("aria-labelledby");
|
|
44213
44261
|
const hasLabel = id ? $2(`label[for="${id}"]`).length > 0 : false;
|
|
44214
|
-
|
|
44215
|
-
|
|
44216
|
-
addFinding("form-label", "critical", "Formularelement ohne zugeh\xF6riges Label", $2.html(el)?.slice(0, 200) ?? "", "input", "1.3.1", "Verkn\xFCpfen Sie das Element mit einem <label> oder aria-label");
|
|
44262
|
+
if (!hasLabel && !$2(el).closest("label").length && !$2(el).attr("aria-label") && !$2(el).attr("aria-labelledby")) {
|
|
44263
|
+
addFinding("form-label", "critical", "Formularelement ohne Label", $2.html(el)?.slice(0, 200) ?? "", "input", "1.3.1", "Label oder aria-label verkn\xFCpfen");
|
|
44217
44264
|
}
|
|
44218
44265
|
});
|
|
44219
|
-
|
|
44220
|
-
|
|
44221
|
-
if (!hasMain) {
|
|
44222
|
-
addFinding("landmark-main", "moderate", "Kein <main>-Landmark gefunden", "", "", "1.3.1", "Verwenden Sie ein <main>-Element f\xFCr den Hauptinhalt");
|
|
44223
|
-
}
|
|
44224
|
-
if (!hasNav) {
|
|
44225
|
-
addFinding("landmark-nav", "minor", "Kein <nav>-Landmark gefunden", "", "", "1.3.1", "Verwenden Sie ein <nav>-Element f\xFCr die Navigation");
|
|
44226
|
-
}
|
|
44266
|
+
if (!$2('main, [role="main"]').length) addFinding("landmark-main", "moderate", "Kein <main>-Landmark", "", "", "1.3.1", "<main>-Element verwenden");
|
|
44267
|
+
if (!$2('nav, [role="navigation"]').length) addFinding("landmark-nav", "minor", "Kein <nav>-Landmark", "", "", "1.3.1", "<nav>-Element verwenden");
|
|
44227
44268
|
$2('button, [role="button"]').each((_, el) => {
|
|
44228
|
-
|
|
44229
|
-
|
|
44230
|
-
const title2 = $2(el).attr("title")?.trim();
|
|
44231
|
-
if (!text3 && !ariaLabel && !title2) {
|
|
44232
|
-
addFinding("button-text", "critical", "Button ohne erkennbaren Text", $2.html(el)?.slice(0, 200) ?? "", "button", "4.1.2", "F\xFCgen Sie Text oder aria-label zum Button hinzu");
|
|
44269
|
+
if (!$2(el).text().trim() && !$2(el).attr("aria-label")?.trim() && !$2(el).attr("title")?.trim()) {
|
|
44270
|
+
addFinding("button-text", "critical", "Button ohne Text", $2.html(el)?.slice(0, 200) ?? "", "button", "4.1.2", "Text oder aria-label hinzuf\xFCgen");
|
|
44233
44271
|
}
|
|
44234
44272
|
});
|
|
44235
|
-
const
|
|
44236
|
-
if (!viewport)
|
|
44237
|
-
|
|
44238
|
-
|
|
44239
|
-
addFinding("viewport-zoom", "critical", "Zoom ist deaktiviert (user-scalable=no oder maximum-scale=1)", "", "meta", "1.4.4", "Entfernen Sie user-scalable=no und maximum-scale=1");
|
|
44273
|
+
const vp = $2('meta[name="viewport"]').attr("content") || "";
|
|
44274
|
+
if (!vp) addFinding("viewport", "moderate", "Kein viewport meta-Tag", "", "meta", "1.4.10", "viewport meta-Tag hinzuf\xFCgen");
|
|
44275
|
+
else if (vp.includes("user-scalable=no") || vp.includes("maximum-scale=1")) {
|
|
44276
|
+
addFinding("viewport-zoom", "critical", "Zoom deaktiviert", "", "meta", "1.4.4", "user-scalable=no entfernen");
|
|
44240
44277
|
}
|
|
44278
|
+
const validRoles = /* @__PURE__ */ new Set(["alert", "alertdialog", "application", "article", "banner", "button", "cell", "checkbox", "columnheader", "combobox", "complementary", "contentinfo", "definition", "dialog", "directory", "document", "feed", "figure", "form", "grid", "gridcell", "group", "heading", "img", "link", "list", "listbox", "listitem", "log", "main", "marquee", "math", "menu", "menubar", "menuitem", "menuitemcheckbox", "menuitemradio", "navigation", "none", "note", "option", "presentation", "progressbar", "radio", "radiogroup", "region", "row", "rowgroup", "rowheader", "scrollbar", "search", "searchbox", "separator", "slider", "spinbutton", "status", "switch", "tab", "table", "tablist", "tabpanel", "term", "textbox", "timer", "toolbar", "tooltip", "tree", "treegrid", "treeitem"]);
|
|
44241
44279
|
$2("[role]").each((_, el) => {
|
|
44242
44280
|
const role = $2(el).attr("role") || "";
|
|
44243
|
-
|
|
44244
|
-
"
|
|
44245
|
-
"alertdialog",
|
|
44246
|
-
"application",
|
|
44247
|
-
"article",
|
|
44248
|
-
"banner",
|
|
44249
|
-
"button",
|
|
44250
|
-
"cell",
|
|
44251
|
-
"checkbox",
|
|
44252
|
-
"columnheader",
|
|
44253
|
-
"combobox",
|
|
44254
|
-
"complementary",
|
|
44255
|
-
"contentinfo",
|
|
44256
|
-
"definition",
|
|
44257
|
-
"dialog",
|
|
44258
|
-
"directory",
|
|
44259
|
-
"document",
|
|
44260
|
-
"feed",
|
|
44261
|
-
"figure",
|
|
44262
|
-
"form",
|
|
44263
|
-
"grid",
|
|
44264
|
-
"gridcell",
|
|
44265
|
-
"group",
|
|
44266
|
-
"heading",
|
|
44267
|
-
"img",
|
|
44268
|
-
"link",
|
|
44269
|
-
"list",
|
|
44270
|
-
"listbox",
|
|
44271
|
-
"listitem",
|
|
44272
|
-
"log",
|
|
44273
|
-
"main",
|
|
44274
|
-
"marquee",
|
|
44275
|
-
"math",
|
|
44276
|
-
"menu",
|
|
44277
|
-
"menubar",
|
|
44278
|
-
"menuitem",
|
|
44279
|
-
"menuitemcheckbox",
|
|
44280
|
-
"menuitemradio",
|
|
44281
|
-
"navigation",
|
|
44282
|
-
"none",
|
|
44283
|
-
"note",
|
|
44284
|
-
"option",
|
|
44285
|
-
"presentation",
|
|
44286
|
-
"progressbar",
|
|
44287
|
-
"radio",
|
|
44288
|
-
"radiogroup",
|
|
44289
|
-
"region",
|
|
44290
|
-
"row",
|
|
44291
|
-
"rowgroup",
|
|
44292
|
-
"rowheader",
|
|
44293
|
-
"scrollbar",
|
|
44294
|
-
"search",
|
|
44295
|
-
"searchbox",
|
|
44296
|
-
"separator",
|
|
44297
|
-
"slider",
|
|
44298
|
-
"spinbutton",
|
|
44299
|
-
"status",
|
|
44300
|
-
"switch",
|
|
44301
|
-
"tab",
|
|
44302
|
-
"table",
|
|
44303
|
-
"tablist",
|
|
44304
|
-
"tabpanel",
|
|
44305
|
-
"term",
|
|
44306
|
-
"textbox",
|
|
44307
|
-
"timer",
|
|
44308
|
-
"toolbar",
|
|
44309
|
-
"tooltip",
|
|
44310
|
-
"tree",
|
|
44311
|
-
"treegrid",
|
|
44312
|
-
"treeitem"
|
|
44313
|
-
];
|
|
44314
|
-
if (!validRoles.includes(role)) {
|
|
44315
|
-
addFinding("aria-role", "serious", `Ung\xFCltige ARIA-Rolle: "${role}"`, $2.html(el)?.slice(0, 200) ?? "", `[role="${role}"]`, "4.1.2", "Verwenden Sie eine g\xFCltige ARIA-Rolle");
|
|
44281
|
+
if (!validRoles.has(role)) {
|
|
44282
|
+
addFinding("aria-role", "serious", `Ung\xFCltige ARIA-Rolle: "${role}"`, $2.html(el)?.slice(0, 200) ?? "", `[role="${role}"]`, "4.1.2", "G\xFCltige ARIA-Rolle verwenden");
|
|
44316
44283
|
}
|
|
44317
44284
|
});
|
|
44318
44285
|
return findings;
|
|
@@ -44320,16 +44287,11 @@ function runRules(html3, url, $2) {
|
|
|
44320
44287
|
async function scanUrl(url) {
|
|
44321
44288
|
const start = Date.now();
|
|
44322
44289
|
const response = await fetch(url, {
|
|
44323
|
-
headers: {
|
|
44324
|
-
"User-Agent": "AccessPilot-Dev/1.0",
|
|
44325
|
-
"Accept": "text/html,application/xhtml+xml"
|
|
44326
|
-
},
|
|
44290
|
+
headers: { "User-Agent": "ACPilot-Dev/1.0", "Accept": "text/html,application/xhtml+xml" },
|
|
44327
44291
|
signal: AbortSignal.timeout(15e3),
|
|
44328
44292
|
redirect: "follow"
|
|
44329
44293
|
});
|
|
44330
|
-
if (!response.ok) {
|
|
44331
|
-
throw new Error(`HTTP ${response.status} f\xFCr ${url}`);
|
|
44332
|
-
}
|
|
44294
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
44333
44295
|
const html3 = await response.text();
|
|
44334
44296
|
const $2 = load(html3);
|
|
44335
44297
|
const pageTitle = $2("title").text().trim() || "Kein Titel";
|
|
@@ -44337,16 +44299,7 @@ async function scanUrl(url) {
|
|
|
44337
44299
|
const score = calculateScore(findings);
|
|
44338
44300
|
const counts = { critical: 0, serious: 0, moderate: 0, minor: 0 };
|
|
44339
44301
|
for (const f of findings) counts[f.severity]++;
|
|
44340
|
-
return {
|
|
44341
|
-
url,
|
|
44342
|
-
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
44343
|
-
score,
|
|
44344
|
-
totalFindings: findings.length,
|
|
44345
|
-
...counts,
|
|
44346
|
-
findings,
|
|
44347
|
-
pageTitle,
|
|
44348
|
-
loadTimeMs: Date.now() - start
|
|
44349
|
-
};
|
|
44302
|
+
return { url, scannedAt: (/* @__PURE__ */ new Date()).toISOString(), score, totalFindings: findings.length, ...counts, findings, pageTitle, loadTimeMs: Date.now() - start };
|
|
44350
44303
|
}
|
|
44351
44304
|
async function crawlUrls(baseUrl, maxPages, maxDepth, log) {
|
|
44352
44305
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -44377,16 +44330,11 @@ async function crawlUrls(baseUrl, maxPages, maxDepth, log) {
|
|
|
44377
44330
|
while (queue.length > 0 && discovered.length < maxPages) {
|
|
44378
44331
|
const { url, depth } = queue.shift();
|
|
44379
44332
|
if (depth >= maxDepth) continue;
|
|
44380
|
-
log(`
|
|
44333
|
+
log(` ${GRAY}\u21B3 ${url.replace(base.origin, "") || "/"}${RESET}`);
|
|
44381
44334
|
try {
|
|
44382
|
-
const res = await fetch(url, {
|
|
44383
|
-
headers: { "User-Agent": "AccessPilot-Dev/1.0", "Accept": "text/html" },
|
|
44384
|
-
signal: AbortSignal.timeout(1e4),
|
|
44385
|
-
redirect: "follow"
|
|
44386
|
-
});
|
|
44335
|
+
const res = await fetch(url, { headers: { "User-Agent": "ACPilot-Dev/1.0", "Accept": "text/html" }, signal: AbortSignal.timeout(1e4), redirect: "follow" });
|
|
44387
44336
|
if (!res.ok) continue;
|
|
44388
|
-
|
|
44389
|
-
if (!ct.includes("text/html")) continue;
|
|
44337
|
+
if (!(res.headers.get("content-type") || "").includes("text/html")) continue;
|
|
44390
44338
|
const html3 = await res.text();
|
|
44391
44339
|
const $2 = load(html3);
|
|
44392
44340
|
$2("a[href]").each((_, el) => {
|
|
@@ -44405,27 +44353,80 @@ async function crawlUrls(baseUrl, maxPages, maxDepth, log) {
|
|
|
44405
44353
|
}
|
|
44406
44354
|
return discovered;
|
|
44407
44355
|
}
|
|
44408
|
-
|
|
44409
|
-
|
|
44410
|
-
|
|
44411
|
-
|
|
44412
|
-
|
|
44413
|
-
|
|
44414
|
-
|
|
44415
|
-
|
|
44416
|
-
|
|
44417
|
-
|
|
44418
|
-
|
|
44356
|
+
function progressBar(current, total, width = 30) {
|
|
44357
|
+
const pct = Math.round(current / total * 100);
|
|
44358
|
+
const filled = Math.round(current / total * width);
|
|
44359
|
+
const bar = `${CYAN}${"\u2588".repeat(filled)}${GRAY}${"\u2591".repeat(width - filled)}${RESET}`;
|
|
44360
|
+
return ` ${bar} ${BRIGHT_WHITE}${pct}%${RESET} ${DIM}(${current}/${total})${RESET}`;
|
|
44361
|
+
}
|
|
44362
|
+
function bigScore(score) {
|
|
44363
|
+
const c = scoreColor(score);
|
|
44364
|
+
const label = score >= 90 ? "Sehr gut" : score >= 70 ? "Verbesserungsbedarf" : "Kritisch";
|
|
44365
|
+
return ` ${c}${BOLD} \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
44366
|
+
\u2551 ${score}/100 \u2551
|
|
44367
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D${RESET}
|
|
44368
|
+
${c}${label}${RESET}`;
|
|
44419
44369
|
}
|
|
44420
44370
|
async function main() {
|
|
44421
44371
|
const args = parseArgs();
|
|
44422
|
-
if (
|
|
44423
|
-
|
|
44424
|
-
|
|
44425
|
-
|
|
44426
|
-
|
|
44427
|
-
|
|
44428
|
-
|
|
44372
|
+
if (args.json && args.url && args.apiKey) {
|
|
44373
|
+
return runScan(args);
|
|
44374
|
+
}
|
|
44375
|
+
printLogo();
|
|
44376
|
+
let url = args.url;
|
|
44377
|
+
let apiKey = args.apiKey;
|
|
44378
|
+
let mode = args.noCrawl ? "single" : "crawl";
|
|
44379
|
+
let maxPages = args.maxPages;
|
|
44380
|
+
let maxDepth = args.maxDepth;
|
|
44381
|
+
if (args.interactive || !url || !apiKey) {
|
|
44382
|
+
const rl = createRL();
|
|
44383
|
+
console.log(` ${DIM}Willkommen beim ACPilot Dev Scanner!${RESET}`);
|
|
44384
|
+
console.log(` ${DIM}Scanne deine lokale Website auf WCAG-Barrierefreiheit.${RESET}`);
|
|
44385
|
+
printDivider("Konfiguration");
|
|
44386
|
+
url = await ask(rl, `${BOLD}URL deines Dev-Servers${RESET}`, "http://localhost:3000");
|
|
44387
|
+
try {
|
|
44388
|
+
new URL(url);
|
|
44389
|
+
} catch {
|
|
44390
|
+
console.error(`
|
|
44391
|
+
${RED}\u2717${RESET} Ung\xFCltige URL: ${url}`);
|
|
44392
|
+
rl.close();
|
|
44393
|
+
process.exit(1);
|
|
44394
|
+
}
|
|
44395
|
+
if (!apiKey) {
|
|
44396
|
+
apiKey = await ask(rl, `${BOLD}API-Key${RESET} ${DIM}(von acpilot.de/dashboard/dev)${RESET}`);
|
|
44397
|
+
}
|
|
44398
|
+
if (!apiKey) {
|
|
44399
|
+
console.error(`
|
|
44400
|
+
${RED}\u2717${RESET} API-Key ist erforderlich.`);
|
|
44401
|
+
console.log(` ${DIM}Erstelle einen auf: ${CYAN}https://acpilot.de/dashboard/dev${RESET}`);
|
|
44402
|
+
rl.close();
|
|
44403
|
+
process.exit(1);
|
|
44404
|
+
}
|
|
44405
|
+
console.log("");
|
|
44406
|
+
const modeChoice = await askChoice(rl, `${BOLD}Scan-Modus w\xE4hlen${RESET}`, [
|
|
44407
|
+
{ key: "1", label: "Komplette Website", desc: "\u2014 Crawlt alle Unterseiten automatisch" },
|
|
44408
|
+
{ key: "2", label: "Einzelne Seite", desc: "\u2014 Nur die eingegebene URL scannen" }
|
|
44409
|
+
]);
|
|
44410
|
+
mode = modeChoice === "2" ? "single" : "crawl";
|
|
44411
|
+
if (mode === "crawl") {
|
|
44412
|
+
const pagesStr = await ask(rl, "Max. Seitenanzahl", "50");
|
|
44413
|
+
maxPages = parseInt(pagesStr, 10) || 50;
|
|
44414
|
+
const depthStr = await ask(rl, "Max. Crawl-Tiefe", "3");
|
|
44415
|
+
maxDepth = parseInt(depthStr, 10) || 3;
|
|
44416
|
+
}
|
|
44417
|
+
rl.close();
|
|
44418
|
+
args.url = url;
|
|
44419
|
+
args.apiKey = apiKey;
|
|
44420
|
+
args.noCrawl = mode === "single";
|
|
44421
|
+
args.maxPages = maxPages;
|
|
44422
|
+
args.maxDepth = maxDepth;
|
|
44423
|
+
}
|
|
44424
|
+
return runScan(args);
|
|
44425
|
+
}
|
|
44426
|
+
async function runScan(args) {
|
|
44427
|
+
const isJson = args.json;
|
|
44428
|
+
printDivider("Verbindung");
|
|
44429
|
+
process.stdout.write(` ${CYAN}\u27F3${RESET} API-Key wird verifiziert... `);
|
|
44429
44430
|
try {
|
|
44430
44431
|
const verifyRes = await fetch(`${args.server}/api/dev-agent/verify`, {
|
|
44431
44432
|
method: "POST",
|
|
@@ -44434,118 +44435,116 @@ ${BOLD} AccessPilot Dev Scanner${RESET}`);
|
|
|
44434
44435
|
});
|
|
44435
44436
|
const verifyData = await verifyRes.json();
|
|
44436
44437
|
if (!verifyData.valid) {
|
|
44437
|
-
|
|
44438
|
+
console.log(`${RED}\u2717 ung\xFCltig${RESET}`);
|
|
44438
44439
|
console.error(`
|
|
44439
|
-
Fehler
|
|
44440
|
+
${RED}Fehler:${RESET} ${verifyData.error || "Ung\xFCltiger API-Key"}`);
|
|
44441
|
+
console.log(` ${DIM}Erstelle einen neuen Key auf: ${CYAN}https://acpilot.de/dashboard/dev${RESET}`);
|
|
44440
44442
|
process.exit(1);
|
|
44441
44443
|
}
|
|
44442
|
-
|
|
44443
|
-
} catch
|
|
44444
|
-
|
|
44444
|
+
console.log(`${GREEN}\u2713 verifiziert${RESET}`);
|
|
44445
|
+
} catch {
|
|
44446
|
+
console.log(`${RED}\u2717 Fehler${RESET}`);
|
|
44445
44447
|
console.error(`
|
|
44446
|
-
Verbindung zu ${args.server} fehlgeschlagen
|
|
44447
|
-
console.error(` Ist der AccessPilot-Server erreichbar?
|
|
44448
|
-
`);
|
|
44448
|
+
${RED}Verbindung zu ${args.server} fehlgeschlagen.${RESET}`);
|
|
44449
44449
|
process.exit(1);
|
|
44450
44450
|
}
|
|
44451
44451
|
let urls;
|
|
44452
44452
|
if (args.noCrawl) {
|
|
44453
44453
|
urls = [args.url];
|
|
44454
|
-
|
|
44455
|
-
`);
|
|
44454
|
+
console.log(` ${CYAN}\u25C9${RESET} Einzelscan: ${BOLD}${args.url}${RESET}`);
|
|
44456
44455
|
} else {
|
|
44457
|
-
|
|
44458
|
-
|
|
44456
|
+
printDivider("URL Discovery");
|
|
44457
|
+
console.log(` ${CYAN}\u27F3${RESET} Crawle ${BOLD}${args.url}${RESET} ${DIM}(max ${args.maxPages} Seiten, Tiefe ${args.maxDepth})${RESET}
|
|
44458
|
+
`);
|
|
44459
44459
|
urls = await crawlUrls(args.url, args.maxPages, args.maxDepth, (msg) => {
|
|
44460
|
-
if (!
|
|
44460
|
+
if (!isJson) console.log(msg);
|
|
44461
44461
|
});
|
|
44462
|
-
|
|
44463
|
-
${GREEN}${urls.length}
|
|
44464
|
-
`);
|
|
44462
|
+
console.log(`
|
|
44463
|
+
${GREEN}\u2713${RESET} ${BOLD}${urls.length}${RESET} Seiten entdeckt`);
|
|
44465
44464
|
}
|
|
44466
44465
|
if (urls.length === 0) {
|
|
44467
|
-
console.error(
|
|
44466
|
+
console.error(`
|
|
44467
|
+
${RED}\u2717${RESET} Keine Seiten gefunden. Ist der Dev-Server gestartet?`);
|
|
44468
44468
|
process.exit(1);
|
|
44469
44469
|
}
|
|
44470
|
-
|
|
44470
|
+
printDivider("Scanning");
|
|
44471
44471
|
const results = [];
|
|
44472
44472
|
const CONCURRENCY = 5;
|
|
44473
|
+
let scanned = 0;
|
|
44474
|
+
const origin = new URL(args.url).origin;
|
|
44473
44475
|
for (let i = 0; i < urls.length; i += CONCURRENCY) {
|
|
44474
44476
|
const chunk = urls.slice(i, i + CONCURRENCY);
|
|
44475
|
-
const
|
|
44476
|
-
|
|
44477
|
-
|
|
44478
|
-
|
|
44479
|
-
const
|
|
44480
|
-
|
|
44481
|
-
|
|
44482
|
-
const r = settled.value;
|
|
44477
|
+
const settled = await Promise.allSettled(chunk.map((u) => scanUrl(u)));
|
|
44478
|
+
for (let j = 0; j < settled.length; j++) {
|
|
44479
|
+
scanned++;
|
|
44480
|
+
const s = settled[j];
|
|
44481
|
+
const path = chunk[j].replace(origin, "") || "/";
|
|
44482
|
+
if (s.status === "fulfilled") {
|
|
44483
|
+
const r = s.value;
|
|
44483
44484
|
results.push(r);
|
|
44484
|
-
|
|
44485
|
-
|
|
44486
|
-
|
|
44487
|
-
console.log(` ${sc}${r.score}${RESET} ${path} ${DIM}(${r.totalFindings} Findings, ${r.loadTimeMs}ms)${RESET}`);
|
|
44488
|
-
}
|
|
44485
|
+
const sc = scoreColor(r.score);
|
|
44486
|
+
const severity = r.critical > 0 ? `${RED}${r.critical} krit.${RESET}` : r.serious > 0 ? `${YELLOW}${r.serious} ernst${RESET}` : `${GREEN}sauber${RESET}`;
|
|
44487
|
+
console.log(` ${sc}${String(r.score).padStart(3)}${RESET} ${path.padEnd(35)} ${severity} ${DIM}${r.loadTimeMs}ms${RESET}`);
|
|
44489
44488
|
} else {
|
|
44490
|
-
|
|
44491
|
-
const path = pageUrl.replace(new URL(args.url).origin, "") || "/";
|
|
44492
|
-
console.log(` ${RED}ERR${RESET} ${path} ${DIM}${settled.reason instanceof Error ? settled.reason.message : "Fehler"}${RESET}`);
|
|
44493
|
-
}
|
|
44489
|
+
console.log(` ${RED}ERR${RESET} ${path.padEnd(35)} ${DIM}${s.reason instanceof Error ? s.reason.message : "Fehler"}${RESET}`);
|
|
44494
44490
|
}
|
|
44495
44491
|
}
|
|
44492
|
+
if (!isJson && urls.length > CONCURRENCY) {
|
|
44493
|
+
console.log(progressBar(scanned, urls.length));
|
|
44494
|
+
}
|
|
44496
44495
|
}
|
|
44497
44496
|
if (results.length === 0) {
|
|
44498
|
-
console.error(
|
|
44497
|
+
console.error(`
|
|
44498
|
+
${RED}\u2717${RESET} Keine Seiten konnten gescannt werden.`);
|
|
44499
44499
|
process.exit(1);
|
|
44500
44500
|
}
|
|
44501
|
-
|
|
44502
|
-
Ergebnisse werden hochgeladen... `);
|
|
44501
|
+
printDivider("Upload");
|
|
44502
|
+
process.stdout.write(` ${CYAN}\u27F3${RESET} Ergebnisse werden hochgeladen... `);
|
|
44503
44503
|
try {
|
|
44504
44504
|
const pushRes = await fetch(`${args.server}/api/dev-agent/results`, {
|
|
44505
44505
|
method: "POST",
|
|
44506
|
-
headers: {
|
|
44507
|
-
|
|
44508
|
-
"Content-Type": "application/json"
|
|
44509
|
-
},
|
|
44510
|
-
body: JSON.stringify({
|
|
44511
|
-
base_url: args.url,
|
|
44512
|
-
pages: results,
|
|
44513
|
-
project_id: args.projectId
|
|
44514
|
-
})
|
|
44506
|
+
headers: { "Authorization": `Bearer ${args.apiKey}`, "Content-Type": "application/json" },
|
|
44507
|
+
body: JSON.stringify({ base_url: args.url, pages: results, project_id: args.projectId })
|
|
44515
44508
|
});
|
|
44516
44509
|
const pushData = await pushRes.json();
|
|
44517
44510
|
if (!pushRes.ok) {
|
|
44518
|
-
|
|
44511
|
+
console.log(`${RED}\u2717${RESET}`);
|
|
44519
44512
|
console.error(` ${pushData.error || "Upload fehlgeschlagen"}`);
|
|
44520
44513
|
process.exit(1);
|
|
44521
44514
|
}
|
|
44522
|
-
|
|
44515
|
+
console.log(`${GREEN}\u2713 hochgeladen${RESET}`);
|
|
44516
|
+
if (isJson) {
|
|
44523
44517
|
console.log(JSON.stringify({ ...pushData, results }, null, 2));
|
|
44524
|
-
|
|
44525
|
-
console.log(`${GREEN}OK${RESET}`);
|
|
44526
|
-
const avgScore = Math.round(results.reduce((s, r) => s + r.score, 0) / results.length);
|
|
44527
|
-
const totalFindings = results.reduce((s, r) => s + r.totalFindings, 0);
|
|
44528
|
-
const totalCritical = results.reduce((s, r) => s + r.critical, 0);
|
|
44529
|
-
const totalSerious = results.reduce((s, r) => s + r.serious, 0);
|
|
44530
|
-
console.log(`
|
|
44531
|
-
${BOLD} \u2500\u2500 Ergebnis \u2500\u2500${RESET}`);
|
|
44532
|
-
console.log(` Seiten: ${results.length}`);
|
|
44533
|
-
console.log(` Score: ${scoreColor(avgScore)}${avgScore}/100${RESET}`);
|
|
44534
|
-
console.log(` Findings: ${totalFindings} gesamt`);
|
|
44535
|
-
if (totalCritical > 0) console.log(` Kritisch: ${RED}${totalCritical}${RESET}`);
|
|
44536
|
-
if (totalSerious > 0) console.log(` Ernst: ${YELLOW}${totalSerious}${RESET}`);
|
|
44537
|
-
console.log(`
|
|
44538
|
-
${CYAN}Dashboard:${RESET} ${args.server}${pushData.dashboard_url}
|
|
44539
|
-
`);
|
|
44518
|
+
return;
|
|
44540
44519
|
}
|
|
44520
|
+
const avgScore = Math.round(results.reduce((s, r) => s + r.score, 0) / results.length);
|
|
44521
|
+
const totalFindings = results.reduce((s, r) => s + r.totalFindings, 0);
|
|
44522
|
+
const totalCritical = results.reduce((s, r) => s + r.critical, 0);
|
|
44523
|
+
const totalSerious = results.reduce((s, r) => s + r.serious, 0);
|
|
44524
|
+
const totalModerate = results.reduce((s, r) => s + r.moderate, 0);
|
|
44525
|
+
const totalMinor = results.reduce((s, r) => s + r.minor, 0);
|
|
44526
|
+
printDivider("Ergebnis");
|
|
44527
|
+
console.log(bigScore(avgScore));
|
|
44528
|
+
console.log("");
|
|
44529
|
+
console.log(` ${BOLD}Seiten gescannt:${RESET} ${results.length}`);
|
|
44530
|
+
console.log(` ${BOLD}Findings gesamt:${RESET} ${totalFindings}`);
|
|
44531
|
+
if (totalCritical > 0) console.log(` ${RED}\u25CF Kritisch:${RESET} ${totalCritical}`);
|
|
44532
|
+
if (totalSerious > 0) console.log(` ${YELLOW}\u25CF Ernst:${RESET} ${totalSerious}`);
|
|
44533
|
+
if (totalModerate > 0) console.log(` ${CYAN}\u25CF Moderat:${RESET} ${totalModerate}`);
|
|
44534
|
+
if (totalMinor > 0) console.log(` ${GRAY}\u25CF Gering:${RESET} ${totalMinor}`);
|
|
44535
|
+
console.log("");
|
|
44536
|
+
console.log(` ${CYAN}\u2197${RESET} Dashboard: ${BOLD}${args.server}${pushData.dashboard_url}${RESET}`);
|
|
44537
|
+
console.log("");
|
|
44538
|
+
console.log(` ${DIM}Detaillierte Ergebnisse im ACPilot Dashboard ansehen.${RESET}`);
|
|
44539
|
+
console.log("");
|
|
44541
44540
|
} catch (err) {
|
|
44542
|
-
|
|
44541
|
+
console.log(`${RED}\u2717${RESET}`);
|
|
44543
44542
|
console.error(` Upload fehlgeschlagen: ${err instanceof Error ? err.message : err}`);
|
|
44544
44543
|
process.exit(1);
|
|
44545
44544
|
}
|
|
44546
44545
|
}
|
|
44547
44546
|
main().catch((err) => {
|
|
44548
|
-
console.error(
|
|
44547
|
+
console.error(`${RED}Fehler:${RESET} ${err.message}`);
|
|
44549
44548
|
process.exit(1);
|
|
44550
44549
|
});
|
|
44551
44550
|
/*! Bundled license information:
|