acpilot 2.2.0 → 2.3.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.
Files changed (2) hide show
  1. package/dist/cli.js +190 -123
  2. package/package.json +11 -2
package/dist/cli.js CHANGED
@@ -49955,16 +49955,135 @@ if (hasConsent) { addPreconnect("${href}"); }`,
49955
49955
  });
49956
49956
 
49957
49957
  // ../../src/lib/scanner/rules/impressum.ts
49958
+ async function fetchImpressumPage(impressumHref, baseUrl) {
49959
+ try {
49960
+ const absoluteUrl = new URL(impressumHref, baseUrl).href;
49961
+ console.log(`[Impressum] Fetching separate impressum page: ${absoluteUrl}`);
49962
+ const res = await fetch(absoluteUrl, {
49963
+ signal: AbortSignal.timeout(8e3),
49964
+ headers: { "User-Agent": "AccessPilot/1.0 (DSGVO Scanner)" }
49965
+ });
49966
+ if (!res.ok) return null;
49967
+ const contentType = res.headers.get("content-type") || "";
49968
+ if (!contentType.includes("text/html") && !contentType.includes("application/xhtml")) {
49969
+ return null;
49970
+ }
49971
+ const html3 = await res.text();
49972
+ const $2 = load(html3);
49973
+ const text3 = $2("body").text().replace(/\s+/g, " ");
49974
+ return { text: text3, html: html3, $: $2 };
49975
+ } catch (err) {
49976
+ console.warn(`[Impressum] Failed to fetch impressum page: ${err instanceof Error ? err.message : err}`);
49977
+ return null;
49978
+ }
49979
+ }
49980
+ function checkImpressumContent(normalizedText, rawHtml, $content, impressumHref, url, findings, indexRef) {
49981
+ const hasAddress = /\b\d{5}\s+\w/i.test(normalizedText) || // German PLZ (5 digits) + city
49982
+ /\b\d{4}\s+\w/i.test(normalizedText) || // Austrian PLZ (4 digits) + city
49983
+ /straße|str\.\s*\d|weg\s+\d|gasse|platz\s+\d|allee/i.test(normalizedText) || // Street patterns
49984
+ /\bstraße\b|\bstr\b/i.test(normalizedText);
49985
+ if (!hasAddress) {
49986
+ findings.push({
49987
+ id: `impressum-missing-address-${indexRef.value++}`,
49988
+ rule: "impressum",
49989
+ severity: "moderate",
49990
+ impact: "Ohne Anschrift im Impressum fehlt eine Pflichtangabe nach TMG \xA75.",
49991
+ description: "Keine postalische Anschrift im Impressum erkennbar",
49992
+ explanation: "Das Impressum muss die ladungsf\xE4hige Anschrift des Anbieters enthalten (Stra\xDFe, Hausnummer, PLZ und Ort).",
49993
+ element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
49994
+ selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
49995
+ fix: "Stellen Sie sicher, dass im Impressum eine vollst\xE4ndige Anschrift (Stra\xDFe, Hausnummer, PLZ, Ort) angegeben ist.",
49996
+ codeExample: "<p>Musterstra\xDFe 1<br>12345 Musterstadt</p>",
49997
+ wcagReference: "TMG \xA75 / DDG \xA75",
49998
+ wcagLevel: "AA",
49999
+ wcagName: "Impressum Pflichtangaben \u2013 Anschrift",
50000
+ legalBasis: "DSGVO",
50001
+ pageUrl: url
50002
+ });
50003
+ }
50004
+ const hasEmail = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/i.test(normalizedText) || /kontaktformular|contact\s*form/i.test(normalizedText) || /e-?mail\s*:/i.test(normalizedText) || // Check for mailto: links in HTML
50005
+ $content('a[href^="mailto:"]').length > 0 || /mailto:/i.test(rawHtml) || // Obfuscated emails: [at], (at), [dot]
50006
+ /\w+\s*[\[(]\s*at\s*[\])]\s*\w+/i.test(normalizedText);
50007
+ if (!hasEmail) {
50008
+ findings.push({
50009
+ id: `impressum-missing-email-${indexRef.value++}`,
50010
+ rule: "impressum",
50011
+ severity: "moderate",
50012
+ impact: "Ohne E-Mail-Adresse oder Kontaktm\xF6glichkeit fehlt eine Pflichtangabe im Impressum.",
50013
+ description: "Keine E-Mail-Adresse oder Kontaktm\xF6glichkeit im Impressum erkennbar",
50014
+ explanation: "Das Impressum muss eine M\xF6glichkeit zur schnellen elektronischen Kontaktaufnahme enthalten (in der Regel eine E-Mail-Adresse).",
50015
+ element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
50016
+ selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
50017
+ fix: "Geben Sie im Impressum eine E-Mail-Adresse an oder verweisen Sie auf ein Kontaktformular.",
50018
+ codeExample: "<p>E-Mail: info@example.de</p>",
50019
+ wcagReference: "TMG \xA75 / DDG \xA75",
50020
+ wcagLevel: "AA",
50021
+ wcagName: "Impressum Pflichtangaben \u2013 Kontakt",
50022
+ legalBasis: "DSGVO",
50023
+ pageUrl: url
50024
+ });
50025
+ }
50026
+ const hasPhone = (
50027
+ // Labeled patterns
50028
+ /tel\.?\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /telefon\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /phone\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /fon\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /mobil\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /fax\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /handy\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /ruf\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || // International format: +49, +43, +41 etc. (without needing a label)
50029
+ /\+\d{1,3}\s*[(]?\d[\d\s()+\-/]{5,}/i.test(normalizedText) || // German landline/mobile without label: 0800, 030, 089, 0171 etc.
50030
+ /\b0\d{2,4}[\s/\-.]?\d{3,}[\d\s/\-.]*\d/i.test(normalizedText) || // tel: links in HTML
50031
+ $content('a[href^="tel:"]').length > 0 || /href\s*=\s*["']tel:/i.test(rawHtml)
50032
+ );
50033
+ if (!hasPhone) {
50034
+ findings.push({
50035
+ id: `impressum-missing-phone-${indexRef.value++}`,
50036
+ rule: "impressum",
50037
+ severity: "moderate",
50038
+ impact: "Eine Telefonnummer im Impressum wird seit 2024 empfohlen, um eine weitere Kontaktm\xF6glichkeit bereitzustellen.",
50039
+ description: "Keine Telefonnummer im Impressum erkennbar",
50040
+ explanation: "Seit der Novellierung des DDG (2024) wird eine Telefonnummer im Impressum als zus\xE4tzliche Kontaktm\xF6glichkeit empfohlen.",
50041
+ element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
50042
+ selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
50043
+ fix: "Erg\xE4nzen Sie das Impressum um eine Telefonnummer.",
50044
+ codeExample: "<p>Telefon: +49 (0) 30 1234567</p>",
50045
+ wcagReference: "TMG \xA75 / DDG \xA75",
50046
+ wcagLevel: "AA",
50047
+ wcagName: "Impressum Pflichtangaben \u2013 Telefon",
50048
+ legalBasis: "DSGVO",
50049
+ pageUrl: url
50050
+ });
50051
+ }
50052
+ const hasRegister = /handelsregister|amtsgericht|hrb\s*\d|hra\s*\d/i.test(normalizedText) || /ust\.?\s*-?\s*id\.?\s*-?\s*nr/i.test(normalizedText) || /umsatzsteuer/i.test(normalizedText) || /steuernummer|steuer-?nr/i.test(normalizedText) || /vat\s*-?\s*id/i.test(normalizedText) || /DE\s?\d{9}/.test(normalizedText) || // German VAT ID format
50053
+ /ATU\d{8}/.test(normalizedText) || // Austrian VAT ID
50054
+ /CHE[\-.]?\d{3}[\-.]?\d{3}[\-.]?\d{3}/i.test(normalizedText) || // Swiss UID
50055
+ /registernummer|register-?nr/i.test(normalizedText) || /eingetragen\s*(im|beim)/i.test(normalizedText);
50056
+ if (!hasRegister) {
50057
+ findings.push({
50058
+ id: `impressum-missing-register-${indexRef.value++}`,
50059
+ rule: "impressum",
50060
+ severity: "moderate",
50061
+ impact: "Fehlende Handelsregister- oder USt-IdNr.-Angaben k\xF6nnen einen Versto\xDF gegen TMG \xA75 darstellen.",
50062
+ description: "Keine Handelsregister- oder USt-IdNr.-Angaben im Impressum erkennbar",
50063
+ explanation: "Sofern eine Handelsregistereintragung oder USt-IdNr. vorliegt, sind diese im Impressum anzugeben (TMG \xA75 Abs. 1 Nr. 4 und 6). Hinweis: Nicht alle Anbieter sind registerpflichtig.",
50064
+ element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
50065
+ selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
50066
+ fix: "Erg\xE4nzen Sie das Impressum um die Handelsregisternummer und/oder USt-IdNr., sofern vorhanden.",
50067
+ codeExample: "<p>Registergericht: Amtsgericht M\xFCnchen, HRB 123456</p>\n<p>USt-IdNr.: DE123456789</p>",
50068
+ wcagReference: "TMG \xA75 / DDG \xA75",
50069
+ wcagLevel: "AA",
50070
+ wcagName: "Impressum Pflichtangaben \u2013 Register/USt-IdNr.",
50071
+ legalBasis: "DSGVO",
50072
+ pageUrl: url
50073
+ });
50074
+ }
50075
+ }
49958
50076
  var impressumRule;
49959
50077
  var init_impressum = __esm({
49960
50078
  "../../src/lib/scanner/rules/impressum.ts"() {
49961
50079
  "use strict";
50080
+ init_esm11();
49962
50081
  impressumRule = {
49963
50082
  id: "impressum",
49964
50083
  name: "Impressum & Datenschutzlink",
49965
- run: (html3, url, $2) => {
50084
+ run: async (html3, url, $2) => {
49966
50085
  const findings = [];
49967
- let index2 = 0;
50086
+ const indexRef = { value: 0 };
49968
50087
  const allLinks = $2("a");
49969
50088
  let impressumLink = null;
49970
50089
  let impressumHref = "";
@@ -50049,33 +50168,41 @@ var init_impressum = __esm({
50049
50168
  }
50050
50169
  }
50051
50170
  if (!impressumLink && !impressumSectionFound) {
50052
- $2('dialog, [role="dialog"], .modal, .accordion, details, [data-modal], [data-dialog]').each((_, el) => {
50053
- if (impressumSectionFound) return;
50054
- const text3 = $2(el).text().toLowerCase();
50055
- if (impressumTextPatterns.test(text3)) {
50056
- impressumSectionFound = true;
50057
- impressumLink = el;
50171
+ $2('dialog, [role="dialog"], .modal, .accordion, details, [data-modal], [data-dialog]').each(
50172
+ (_, el) => {
50173
+ if (impressumSectionFound) return;
50174
+ const text3 = $2(el).text().toLowerCase();
50175
+ if (impressumTextPatterns.test(text3)) {
50176
+ impressumSectionFound = true;
50177
+ impressumLink = el;
50178
+ }
50058
50179
  }
50059
- });
50180
+ );
50060
50181
  }
50061
50182
  if (!impressumLink && !impressumSectionFound) {
50062
- $2('[data-target*="impressum"], [data-section*="impressum"], [aria-controls*="impressum"], [data-target*="imprint"], [data-section*="imprint"], [aria-controls*="imprint"]').each((_, el) => {
50183
+ $2(
50184
+ '[data-target*="impressum"], [data-section*="impressum"], [aria-controls*="impressum"], [data-target*="imprint"], [data-section*="imprint"], [aria-controls*="imprint"]'
50185
+ ).each((_, el) => {
50063
50186
  if (impressumSectionFound) return;
50064
50187
  impressumSectionFound = true;
50065
50188
  impressumLink = el;
50066
50189
  });
50067
50190
  }
50068
50191
  if (!impressumLink && !impressumSectionFound) {
50069
- $2('[href*="impressum"], [href*="imprint"], [to*="impressum"], [to*="imprint"]').each((_, el) => {
50070
- if (impressumLink) return;
50071
- impressumLink = el;
50072
- impressumHref = $2(el).attr("href") || $2(el).attr("to") || "";
50073
- });
50192
+ $2('[href*="impressum"], [href*="imprint"], [to*="impressum"], [to*="imprint"]').each(
50193
+ (_, el) => {
50194
+ if (impressumLink) return;
50195
+ impressumLink = el;
50196
+ impressumHref = $2(el).attr("href") || $2(el).attr("to") || "";
50197
+ }
50198
+ );
50074
50199
  }
50075
- console.log(`[Impressum] Detection for ${url}: link=${!!impressumLink}, section=${impressumSectionFound}, href="${impressumHref}"`);
50200
+ console.log(
50201
+ `[Impressum] Detection for ${url}: link=${!!impressumLink}, section=${impressumSectionFound}, href="${impressumHref}"`
50202
+ );
50076
50203
  if (!impressumLink && !impressumSectionFound) {
50077
50204
  findings.push({
50078
- id: `impressum-missing-link-${index2++}`,
50205
+ id: `impressum-missing-link-${indexRef.value++}`,
50079
50206
  rule: "impressum",
50080
50207
  severity: "critical",
50081
50208
  impact: "Ohne Impressum-Link verst\xF6\xDFt die Website gegen die gesetzliche Anbieterkennzeichnungspflicht nach TMG \xA75 / DDG \xA75.",
@@ -50155,14 +50282,16 @@ var init_impressum = __esm({
50155
50282
  });
50156
50283
  }
50157
50284
  if (!datenschutzLinkFound) {
50158
- $2('[href*="datenschutz"], [href*="privacy"], [to*="datenschutz"], [to*="privacy"]').each((_, el) => {
50159
- if (datenschutzLinkFound) return;
50160
- datenschutzLinkFound = true;
50161
- });
50285
+ $2('[href*="datenschutz"], [href*="privacy"], [to*="datenschutz"], [to*="privacy"]').each(
50286
+ (_, el) => {
50287
+ if (datenschutzLinkFound) return;
50288
+ datenschutzLinkFound = true;
50289
+ }
50290
+ );
50162
50291
  }
50163
50292
  if (!datenschutzLinkFound) {
50164
50293
  findings.push({
50165
- id: `impressum-missing-datenschutz-${index2++}`,
50294
+ id: `impressum-missing-datenschutz-${indexRef.value++}`,
50166
50295
  rule: "impressum",
50167
50296
  severity: "serious",
50168
50297
  impact: "Ohne Datenschutzhinweise kann die Website gegen die DSGVO Art. 13/14 Informationspflichten versto\xDFen.",
@@ -50179,98 +50308,31 @@ var init_impressum = __esm({
50179
50308
  pageUrl: url
50180
50309
  });
50181
50310
  }
50182
- const isOnPage = impressumSectionFound || impressumLink && (impressumHref.startsWith("#") || impressumHref === "");
50183
- if (isOnPage && (impressumLink || impressumSectionFound)) {
50184
- const bodyText = $2("body").text();
50185
- let sectionText = bodyText;
50186
- if (impressumHref.startsWith("#") && impressumHref.length > 1) {
50187
- const anchorId = impressumHref.slice(1);
50188
- const section = $2(`#${anchorId}`);
50189
- if (section.length > 0) {
50190
- sectionText = section.text();
50191
- }
50192
- }
50193
- const normalizedText = sectionText.replace(/\s+/g, " ");
50194
- const hasAddress = /\b\d{5}\s+\w/i.test(normalizedText) || // German PLZ (5 digits) followed by city
50195
- /straße|str\.|weg\s+\d|gasse|platz\s+\d|allee/i.test(normalizedText);
50196
- if (!hasAddress) {
50197
- findings.push({
50198
- id: `impressum-missing-address-${index2++}`,
50199
- rule: "impressum",
50200
- severity: "moderate",
50201
- impact: "Ohne Anschrift im Impressum fehlt eine Pflichtangabe nach TMG \xA75.",
50202
- description: "Keine postalische Anschrift im Impressum erkennbar",
50203
- explanation: "Das Impressum muss die ladungsf\xE4hige Anschrift des Anbieters enthalten (Stra\xDFe, Hausnummer, PLZ und Ort). Diese konnte auf der aktuellen Seite nicht identifiziert werden. Hinweis: Falls das Impressum auf einer separaten Unterseite liegt, kann diese Pr\xFCfung nicht greifen.",
50204
- element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
50205
- selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
50206
- fix: "Stellen Sie sicher, dass im Impressum eine vollst\xE4ndige Anschrift (Stra\xDFe, Hausnummer, PLZ, Ort) angegeben ist.",
50207
- codeExample: "<p>Musterstra\xDFe 1<br>12345 Musterstadt</p>",
50208
- wcagReference: "TMG \xA75 / DDG \xA75",
50209
- wcagLevel: "AA",
50210
- wcagName: "Impressum Pflichtangaben \u2013 Anschrift",
50211
- legalBasis: "DSGVO",
50212
- pageUrl: url
50213
- });
50214
- }
50215
- const hasEmail = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/i.test(normalizedText) || /kontaktformular|contact\s*form/i.test(normalizedText) || /e-?mail\s*:/i.test(normalizedText);
50216
- if (!hasEmail) {
50217
- findings.push({
50218
- id: `impressum-missing-email-${index2++}`,
50219
- rule: "impressum",
50220
- severity: "moderate",
50221
- impact: "Ohne E-Mail-Adresse oder Kontaktm\xF6glichkeit fehlt eine Pflichtangabe im Impressum.",
50222
- description: "Keine E-Mail-Adresse oder Kontaktm\xF6glichkeit im Impressum erkennbar",
50223
- explanation: "Das Impressum muss eine M\xF6glichkeit zur schnellen elektronischen Kontaktaufnahme enthalten (in der Regel eine E-Mail-Adresse). Diese konnte auf der aktuellen Seite nicht identifiziert werden.",
50224
- element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
50225
- selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
50226
- fix: "Geben Sie im Impressum eine E-Mail-Adresse an oder verweisen Sie auf ein Kontaktformular.",
50227
- codeExample: "<p>E-Mail: info@example.de</p>",
50228
- wcagReference: "TMG \xA75 / DDG \xA75",
50229
- wcagLevel: "AA",
50230
- wcagName: "Impressum Pflichtangaben \u2013 Kontakt",
50231
- legalBasis: "DSGVO",
50232
- pageUrl: url
50233
- });
50234
- }
50235
- const hasPhone = /tel\.?\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /telefon\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /phone\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText) || /fon\s*:?\s*[\d\s()+\-/]{6,}/i.test(normalizedText);
50236
- if (!hasPhone) {
50237
- findings.push({
50238
- id: `impressum-missing-phone-${index2++}`,
50239
- rule: "impressum",
50240
- severity: "moderate",
50241
- impact: "Eine Telefonnummer im Impressum wird seit 2024 empfohlen, um eine weitere Kontaktm\xF6glichkeit bereitzustellen.",
50242
- description: "Keine Telefonnummer im Impressum erkennbar",
50243
- explanation: "Seit der Novellierung des DDG (2024) wird eine Telefonnummer im Impressum als zus\xE4tzliche Kontaktm\xF6glichkeit empfohlen. Diese konnte auf der aktuellen Seite nicht identifiziert werden.",
50244
- element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
50245
- selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
50246
- fix: "Erg\xE4nzen Sie das Impressum um eine Telefonnummer.",
50247
- codeExample: "<p>Telefon: +49 (0) 30 1234567</p>",
50248
- wcagReference: "TMG \xA75 / DDG \xA75",
50249
- wcagLevel: "AA",
50250
- wcagName: "Impressum Pflichtangaben \u2013 Telefon",
50251
- legalBasis: "DSGVO",
50252
- pageUrl: url
50253
- });
50254
- }
50255
- const hasRegister = /handelsregister|amtsgericht|hrb\s*\d|hra\s*\d/i.test(normalizedText) || /ust\.?\s*-?\s*id\.?\s*-?\s*nr/i.test(normalizedText) || /umsatzsteuer/i.test(normalizedText) || /steuernummer|steuer-?nr/i.test(normalizedText) || /vat\s*-?\s*id/i.test(normalizedText) || /DE\s?\d{9}/.test(normalizedText);
50256
- if (!hasRegister) {
50257
- findings.push({
50258
- id: `impressum-missing-register-${index2++}`,
50259
- rule: "impressum",
50260
- severity: "moderate",
50261
- impact: "Fehlende Handelsregister- oder USt-IdNr.-Angaben k\xF6nnen einen Versto\xDF gegen TMG \xA75 darstellen.",
50262
- description: "Keine Handelsregister- oder USt-IdNr.-Angaben im Impressum erkennbar",
50263
- explanation: "Sofern eine Handelsregistereintragung oder USt-IdNr. vorliegt, sind diese im Impressum anzugeben (TMG \xA75 Abs. 1 Nr. 4 und 6). Diese konnten auf der aktuellen Seite nicht identifiziert werden. Hinweis: Nicht alle Anbieter sind registerpflichtig.",
50264
- element: impressumHref ? `<a href="${impressumHref}">Impressum</a>` : '<section id="impressum">...</section>',
50265
- selector: impressumHref ? `a[href="${impressumHref}"]` : "#impressum",
50266
- fix: "Erg\xE4nzen Sie das Impressum um die Handelsregisternummer und/oder USt-IdNr., sofern vorhanden.",
50267
- codeExample: "<p>Registergericht: Amtsgericht M\xFCnchen, HRB 123456</p>\n<p>USt-IdNr.: DE123456789</p>",
50268
- wcagReference: "TMG \xA75 / DDG \xA75",
50269
- wcagLevel: "AA",
50270
- wcagName: "Impressum Pflichtangaben \u2013 Register/USt-IdNr.",
50271
- legalBasis: "DSGVO",
50272
- pageUrl: url
50273
- });
50311
+ if (impressumLink || impressumSectionFound) {
50312
+ const isOnPage = impressumSectionFound || impressumLink && (impressumHref.startsWith("#") || impressumHref === "");
50313
+ const isSeparatePage = impressumLink && impressumHref && !impressumHref.startsWith("#") && impressumHref !== "";
50314
+ if (isOnPage) {
50315
+ let sectionText = $2("body").text();
50316
+ let sectionHtml = html3;
50317
+ let $section = $2;
50318
+ if (impressumHref.startsWith("#") && impressumHref.length > 1) {
50319
+ const anchorId = impressumHref.slice(1);
50320
+ const section = $2(`#${anchorId}`);
50321
+ if (section.length > 0) {
50322
+ sectionText = section.text();
50323
+ sectionHtml = section.html() || html3;
50324
+ $section = load(section.html() || "");
50325
+ }
50326
+ }
50327
+ const normalizedText = sectionText.replace(/\s+/g, " ");
50328
+ checkImpressumContent(normalizedText, sectionHtml, $section, impressumHref, url, findings, indexRef);
50329
+ } else if (isSeparatePage) {
50330
+ const fetched = await fetchImpressumPage(impressumHref, url);
50331
+ if (fetched) {
50332
+ checkImpressumContent(fetched.text, fetched.html, fetched.$, impressumHref, url, findings, indexRef);
50333
+ } else {
50334
+ console.log(`[Impressum] Could not fetch separate impressum page at "${impressumHref}" \u2014 skipping content checks`);
50335
+ }
50274
50336
  }
50275
50337
  }
50276
50338
  return findings;
@@ -59579,7 +59641,8 @@ async function scanUrl(url) {
59579
59641
  break;
59580
59642
  }
59581
59643
  try {
59582
- rawFindings.push(...rule.run(html3, url, $2));
59644
+ const result = rule.run(html3, url, $2);
59645
+ rawFindings.push(...result instanceof Promise ? await result : result);
59583
59646
  } catch (ruleErr) {
59584
59647
  console.error(`[Scanner] Rule "${rule.id}" failed:`, ruleErr instanceof Error ? ruleErr.message : ruleErr);
59585
59648
  }
@@ -60240,7 +60303,8 @@ async function scanUrlFast(url, options) {
60240
60303
  const rawFindings = [];
60241
60304
  for (const rule of allRules) {
60242
60305
  try {
60243
- rawFindings.push(...rule.run(html3, url, $final));
60306
+ const result = rule.run(html3, url, $final);
60307
+ rawFindings.push(...result instanceof Promise ? await result : result);
60244
60308
  } catch (ruleErr) {
60245
60309
  console.error(`[ScanFast] Rule "${rule.id}" failed:`, ruleErr instanceof Error ? ruleErr.message : ruleErr);
60246
60310
  }
@@ -60373,7 +60437,8 @@ async function scanHtml(html3, url = "local") {
60373
60437
  const rawFindings = [];
60374
60438
  for (const rule of allRules) {
60375
60439
  try {
60376
- rawFindings.push(...rule.run(html3, url, $2));
60440
+ const result = rule.run(html3, url, $2);
60441
+ rawFindings.push(...result instanceof Promise ? await result : result);
60377
60442
  } catch (ruleErr) {
60378
60443
  console.error(`[ScanHtml] Rule "${rule.id}" failed:`, ruleErr instanceof Error ? ruleErr.message : ruleErr);
60379
60444
  }
@@ -60576,7 +60641,7 @@ var init_scanner = __esm({
60576
60641
  var readline = __toESM(require("readline"));
60577
60642
 
60578
60643
  // package.json
60579
- var version = "2.2.0";
60644
+ var version = "2.3.1";
60580
60645
 
60581
60646
  // src/cli.ts
60582
60647
  var PKG_VERSION = version;
@@ -60711,11 +60776,12 @@ function parseArgs() {
60711
60776
  function printHelp() {
60712
60777
  console.log(` ${BOLD}Verwendung:${RESET}`);
60713
60778
  console.log(` ${GREEN}npx acpilot${RESET} ${DIM}Interaktiver Modus${RESET}`);
60714
- console.log(` ${GREEN}npx acpilot${RESET} --url http://localhost:3000 ${DIM}Direkter Modus${RESET}`);
60779
+ console.log(` ${GREEN}npx acpilot${RESET} --url http://localhost:3000 ${DIM}Lokaler Dev-Server${RESET}`);
60780
+ console.log(` ${GREEN}npx acpilot${RESET} --url https://example.com ${DIM}Live-Website${RESET}`);
60715
60781
  console.log(` --api-key AP_xxx`);
60716
60782
  console.log("");
60717
60783
  console.log(` ${BOLD}Optionen:${RESET}`);
60718
- console.log(` ${CYAN}--url${RESET} <url> URL des Dev-Servers`);
60784
+ console.log(` ${CYAN}--url${RESET} <url> URL (localhost oder live, z.B. https://example.com)`);
60719
60785
  console.log(` ${CYAN}--api-key${RESET} <key> API-Key ${DIM}(oder ACCESSPILOT_API_KEY env)${RESET}`);
60720
60786
  console.log(` ${CYAN}--max-pages${RESET} <n> Max. Seiten ${DIM}(Standard: 50)${RESET}`);
60721
60787
  console.log(` ${CYAN}--max-depth${RESET} <n> Crawl-Tiefe ${DIM}(Standard: 3)${RESET}`);
@@ -60789,9 +60855,9 @@ async function main() {
60789
60855
  if (args.interactive || !url) {
60790
60856
  const rl = createRL();
60791
60857
  console.log("");
60792
- console.log(` ${DIM}Scannt deine lokale Website mit der vollen ACPilot Engine.${RESET}`);
60858
+ console.log(` ${DIM}Scannt deine Website mit der vollen ACPilot Engine (lokal oder live).${RESET}`);
60793
60859
  printDivider("Konfiguration");
60794
- url = await ask(rl, `${BOLD}URL deines Dev-Servers${RESET}`, "http://localhost:3000");
60860
+ url = await ask(rl, `${BOLD}URL (localhost oder live)${RESET}`, "http://localhost:3000");
60795
60861
  try {
60796
60862
  new URL(url);
60797
60863
  } catch {
@@ -61071,7 +61137,8 @@ async function runScanInner(args, isJson, puppeteerAvailable) {
61071
61137
  console.log(`
61072
61138
  ${BOLD}${path}${RESET}`);
61073
61139
  for (const f of r.dsgvoFindings) {
61074
- console.log(` ${YELLOW}\u25CF${RESET} ${f.title || f.message || f}`);
61140
+ const severity = f.severity === "critical" ? `${RED}KRITISCH${RESET}` : f.severity === "serious" ? `${YELLOW}ERNST${RESET}` : `${CYAN}MODERAT${RESET}`;
61141
+ console.log(` ${YELLOW}\u25CF${RESET} [${severity}] ${f.description || f.title || f.message || JSON.stringify(f)}`);
61075
61142
  }
61076
61143
  }
61077
61144
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "acpilot",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "description": "Full ACPilot accessibility scanner for local dev servers",
5
5
  "bin": {
6
6
  "acpilot": "./dist/cli.js"
@@ -27,6 +27,15 @@
27
27
  "engines": {
28
28
  "node": ">=18.0.0"
29
29
  },
30
- "keywords": ["accessibility", "a11y", "wcag", "scan", "localhost", "dev", "acpilot", "puppeteer"],
30
+ "keywords": [
31
+ "accessibility",
32
+ "a11y",
33
+ "wcag",
34
+ "scan",
35
+ "localhost",
36
+ "dev",
37
+ "acpilot",
38
+ "puppeteer"
39
+ ],
31
40
  "license": "UNLICENSED"
32
41
  }