#!/usr/bin/env node /** * Sammelpruefung vor jedem Commit: Format, Lint, Typen, Tests, Shellskripte. * Bricht beim ersten Fehler ab und nennt den Schritt, der gefallen ist. * * Die Werkzeuge werden ueber ihren JavaScript-Einstiegspunkt gestartet, nicht * ueber npx. Das spart eine Shell und laeuft unter Windows wie unter Linux * gleich. */ import { spawnSync } from 'node:child_process' import { existsSync, readdirSync, statSync } from 'node:fs' import { join, relative, sep } from 'node:path' import { fileURLToPath } from 'node:url' const WURZEL = fileURLToPath(new URL('..', import.meta.url)) const istWindows = process.platform === 'win32' /** Windows-Pfad in Schraegstrich-Schreibweise, wie Docker sie erwartet. */ function mitSchraegstrich(pfad) { return pfad.split(sep).join('/') } /** Ein mitgeliefertes Werkzeug im selben Node starten. */ function werkzeug(paket, einstieg, argumente) { const ergebnis = spawnSync( process.execPath, [join(WURZEL, 'node_modules', paket, einstieg), ...argumente], { cwd: WURZEL, stdio: 'inherit' }, ) return ergebnis.status ?? 1 } function programm(befehl, argumente, optionen = {}) { const ergebnis = spawnSync(befehl, argumente, { cwd: WURZEL, stdio: 'inherit', ...optionen, }) return ergebnis.status ?? 1 } function vorhanden(befehl) { const ergebnis = spawnSync(istWindows ? 'where' : 'which', [befehl], { cwd: WURZEL, stdio: 'ignore', }) return ergebnis.status === 0 } const UEBERGEHEN = new Set([ 'node_modules', '.git', '.next', 'coverage', 'logs', 'sicherungen', 'playwright-report', 'test-results', ]) /** Alle .sh-Dateien im Projekt, ohne node_modules und Konsorten. */ function shellskripteFinden(verzeichnis = WURZEL, gefunden = []) { for (const eintrag of readdirSync(verzeichnis)) { if (UEBERGEHEN.has(eintrag)) continue const pfad = join(verzeichnis, eintrag) if (statSync(pfad).isDirectory()) { shellskripteFinden(pfad, gefunden) } else if (eintrag.endsWith('.sh')) { gefunden.push(mitSchraegstrich(relative(WURZEL, pfad))) } } return gefunden } function shellskriptePruefen() { const skripte = shellskripteFinden() if (skripte.length === 0) { console.log(' Keine Shellskripte gefunden.') return 0 } console.log(` ${skripte.length} Skript(e): ${skripte.join(', ')}`) const argumente = ['--shell=bash', '--severity=style', ...skripte] if (vorhanden('shellcheck')) { return programm('shellcheck', argumente) } if (programm('docker', ['info'], { stdio: 'ignore' }) === 0) { console.log(' shellcheck ist nicht installiert, nehme das Docker-Abbild.') return programm('docker', [ 'run', '--rm', '-v', `${mitSchraegstrich(WURZEL)}:/mnt`, 'koalaman/shellcheck:stable', ...argumente, ]) } console.error( 'Fehler: Weder shellcheck noch ein laufender Docker-Daemon gefunden. ' + 'Entweder shellcheck installieren (https://www.shellcheck.net/) oder ' + 'Docker starten. Die Shellskripte bleiben sonst ungeprueft.', ) return 1 } const schritte = [ { name: 'Format (Prettier)', tun: () => werkzeug('prettier', 'bin/prettier.cjs', ['--check', '.']), }, { name: 'Lint (ESLint)', tun: () => werkzeug('eslint', 'bin/eslint.js', ['.', '--max-warnings', '0']), }, { name: 'Typen (TypeScript)', tun: () => werkzeug('typescript', 'bin/tsc', ['--noEmit']), }, { name: 'Tests (Vitest)', tun: () => werkzeug('vitest', 'vitest.mjs', ['run']) }, { name: 'Shellskripte (ShellCheck)', tun: shellskriptePruefen }, ] // Ohne erzeugten Prisma-Client schlagen Typen und Tests fehl. if (!existsSync(join(WURZEL, 'src', 'generated', 'prisma', 'client.ts'))) { console.log('▸ Prisma-Client fehlt, erzeuge ihn') if (werkzeug('prisma', 'build/index.js', ['generate']) !== 0) process.exit(1) } let gefallen = null for (const schritt of schritte) { console.log(`▸ ${schritt.name}`) if (schritt.tun() !== 0) { gefallen = schritt.name break } } if (gefallen) { console.error(`✗ Gescheitert: ${gefallen}`) process.exit(1) } console.log('✓ Alle Pruefungen bestanden.')