/* ===================================================================== Prüft, was ein Umstieg auf Express 5 an unserem Code brechen würde. Der Sprung 4 → 5 ist keiner, den man "mal eben" macht: Er entfernt Methoden, ändert das Verhalten bei Pfaden grundlegend (path-to-regexp 8) und stellt Standardwerte um. Vieles davon fällt nicht beim Start auf, sondern erst, wenn eine bestimmte Adresse aufgerufen wird — also im Betrieb, bei einem Kunden. Deshalb wird hier NICHT installiert und ausprobiert, sondern zuerst der Bestand durchsucht. Jede Fundstelle ist eine, die vor dem Umstieg angefasst werden muss. Grundlage: der offizielle Migrationsleitfaden https://expressjs.com/en/guide/migrating-5.html ===================================================================== */ import fs from "fs"; import path from "path"; let treffer = 0, sauber = 0; function dateien(ordner) { const raus = []; const gehe = (p) => { for (const e of fs.readdirSync(p, { withFileTypes: true })) { if (e.name === "node_modules" || e.name.startsWith(".")) continue; const voll = path.join(p, e.name); if (e.isDirectory()) gehe(voll); else if (e.name.endsWith(".js") || e.name.endsWith(".mjs")) raus.push(voll); } }; gehe(ordner); return raus; } const QUELLEN = [ ...dateien("server"), ...dateien("server-internal"), ]; /* Jede Regel: was gesucht wird, warum es bricht, was zu tun ist. */ const REGELN = [ { name: "res.send(zahl) — entfernt", muster: /res\.send\(\s*\d{3}\s*\)/g, hinweis: "ersetzen durch res.sendStatus(zahl)" }, { name: "res.json(objekt, status) — entfernt", muster: /res\.json\([^)]*,\s*\d{3}\s*\)/g, hinweis: "ersetzen durch res.status(...).json(...)" }, { name: "res.send(inhalt, status) — entfernt", muster: /res\.send\([^)]+,\s*\d{3}\s*\)/g, hinweis: "ersetzen durch res.status(...).send(...)" }, /* ⚠️ Die Reihenfolge ist in 5 UMGEKEHRT: res.redirect(status, url). Wer res.redirect(url, status) benutzt, bekommt keinen Fehler -- die Umleitung geht nur an die falsche Stelle. */ { name: "res.redirect(url, status) — Reihenfolge umgedreht", muster: /res\.redirect\(\s*["'`][^"'`]+["'`]\s*,\s*\d{3}\s*\)/g, hinweis: "in Express 5: res.redirect(status, url)" }, { name: 'res.redirect("back") — entfernt', muster: /res\.redirect\(\s*["'`]back["'`]\s*\)/g, hinweis: 'ersetzen durch req.get("Referrer") || "/"' }, { name: "req.param(...) — entfernt", muster: /req\.param\(/g, hinweis: "req.params / req.query / req.body direkt lesen" }, { name: "app.del(...) — entfernt", muster: /\bapp\.del\(/g, hinweis: "app.delete(...) benutzen" }, { name: "res.sendfile(...) — entfernt", muster: /res\.sendfile\(/g, hinweis: "res.sendFile(...) (grosses F)" }, /* path-to-regexp 8: Der namenlose Stern ist weg. */ { name: 'Route mit "*" ohne Namen — Pfadsyntax geaendert', muster: /(app|router)\.(get|post|put|delete|all|use)\(\s*["'`][^"'`]*\*(?![a-zA-Z])/g, hinweis: '"/*" wird zu "/*splat" bzw. "/{*splat}"' }, { name: 'Route mit "?" (optionaler Teil) — Pfadsyntax geaendert', muster: /(app|router)\.(get|post|put|delete|all)\(\s*["'`][^"'`]*\?[^"'`]*["'`]/g, hinweis: '":a?" wird zu "{:a}"' }, { name: "req.query wird beschrieben — jetzt schreibgeschuetzt", muster: /req\.query\s*(\.[a-zA-Z_$][\w$]*)?\s*=[^=]/g, hinweis: "req.query ist in 5 nur noch lesbar" }, { name: "express.static mit hidden/from — Optionen entfernt", muster: /express\.static\([^)]*\b(hidden|from)\s*:/g, hinweis: "dotfiles bzw. root benutzen" }, { name: "express.static.mime — entfernt", muster: /express\.static\.mime/g, hinweis: 'Paket "mime-types" benutzen' }, { name: "req.acceptsCharset/Encoding/Language (Einzahl) — entfernt", muster: /req\.accepts(Charset|Encoding|Language)\(/g, hinweis: "Mehrzahlform benutzen" }, { name: "res.vary() ohne Feld — wirft jetzt einen Fehler", muster: /res\.vary\(\s*\)/g, hinweis: "Feldnamen angeben" }, ]; console.log(`Durchsucht: ${QUELLEN.length} Dateien in server/ und server-internal/\n`); console.log("1. ENTFERNTE UND GEAENDERTE AUFRUFE"); for (const r of REGELN) { const funde = []; for (const datei of QUELLEN) { const inhalt = fs.readFileSync(datei, "utf8"); r.muster.lastIndex = 0; let t; while ((t = r.muster.exec(inhalt)) !== null) { const zeile = inhalt.slice(0, t.index).split("\n").length; funde.push(`${path.relative(process.cwd(), datei)}:${zeile} ${t[0].slice(0, 50)}`); } } if (funde.length) { treffer += funde.length; console.log(` BETROFFEN ${r.name} (${funde.length})`); console.log(` → ${r.hinweis}`); for (const f of funde.slice(0, 4)) console.log(` ${f}`); } else { sauber++; console.log(` ok ${r.name}`); } } /* --------------------------------------------------------------------- Zwei Punkte, die keine Fundstelle haben, aber trotzdem wirken -- deshalb hier ausdrücklich aufgeführt statt übersehen. --------------------------------------------------------------------- */ console.log("\n2. GEAENDERTE STANDARDWERTE (keine Fundstelle, wirken trotzdem)"); const alleInhalte = QUELLEN.map((d) => fs.readFileSync(d, "utf8")).join("\n"); const urlencoded = /express\.urlencoded\(([^)]*)\)/.exec(alleInhalte); if (urlencoded) { const hatExtended = /extended/.test(urlencoded[1]); console.log(hatExtended ? " ok express.urlencoded: extended ist ausdruecklich gesetzt" : ' BETROFFEN express.urlencoded ohne "extended" — Standard wechselt von true auf false\n' + ' → ausdruecklich { extended: true } setzen, wenn verschachtelte Felder gebraucht werden'); if (!hatExtended) treffer++; } else { console.log(" ok express.urlencoded wird nicht benutzt"); } /* dotfiles: In 5 ist der Standard "ignore" -- Pfade, die mit einem Punkt beginnen, liefern dann 404. Das betrifft .well-known, das Let's Encrypt und manche Dienste brauchen. */ const wellknown = fs.existsSync(".well-known"); console.log(wellknown ? ' BETROFFEN Ein Ordner ".well-known" existiert — express.static liefert Punkt-Ordner\n' + ' in Express 5 standardmaessig NICHT mehr aus (dotfiles: "ignore")\n' + ' → { dotfiles: "allow" } fuer diesen Pfad setzen' : ' ok kein ".well-known"-Ordner im Auslieferungsverzeichnis'); if (wellknown) treffer++; /* req.body ist in 5 undefined statt {}, wenn kein Parser lief. */ const bodyOhnePruefung = []; for (const datei of QUELLEN) { const inhalt = fs.readFileSync(datei, "utf8"); const m = /req\.body\.[a-zA-Z_$]/g; let t; while ((t = m.exec(inhalt)) !== null) { if (inhalt.slice(t.index - 1, t.index) === "?") continue; /* ⚠️ Der Blick zurueck muss weit genug reichen. Zuerst standen hier 60 Zeichen. Damit meldete der Pruefer drei Stellen in webdesign-kunden.js als ungesichert -- sie stehen aber INNERHALB eines "if (typeof req.body?.feld === 'boolean')", sind also bereits geprueft. Der Block war nur laenger als der Rueckblick. Ein Pruefer, der abgesicherte Stellen anmahnt, kostet genau die Zeit, die er sparen soll -- und beim naechsten Mal glaubt man ihm auch die echten Funde nicht mehr. Deshalb jetzt die drei Zeilen davor, das deckt den ueblichen Block ab. */ const bis = inhalt.slice(0, t.index); const zeilenDavor = bis.split("\n").slice(-4).join("\n"); if (/req\.body\s*(&&|\?\.)|\|\|\s*\{\}/.test(zeilenDavor)) continue; bodyOhnePruefung.push(`${path.relative(process.cwd(), datei)}:${bis.split("\n").length}`); } } if (bodyOhnePruefung.length) { treffer += bodyOhnePruefung.length; console.log(` BETROFFEN req.body ohne Absicherung (${bodyOhnePruefung.length}) — in 5 ist es\n` + " undefined statt {}, wenn kein Parser lief\n" + " → req.body?.feld schreiben"); for (const f of bodyOhnePruefung.slice(0, 4)) console.log(" " + f); } else { console.log(" ok req.body wird ueberall abgesichert gelesen"); } console.log(`\n===== ${sauber} unbedenklich, ${treffer} Fundstelle(n) zu pruefen =====`); if (!treffer) { console.log("\nDer Umstieg waere aus Sicht dieser Pruefung ohne Codeaenderung moeglich."); console.log("Das ersetzt keinen Testlauf -- es heisst nur, dass die bekannten"); console.log("Bruchstellen aus dem Migrationsleitfaden hier nicht vorkommen."); } process.exit(0);