Merge branch 'phase/001-projekt-setup'

This commit is contained in:
2026-10-12 09:05:15 +02:00
29 changed files with 8127 additions and 9 deletions
+37
View File
@@ -0,0 +1,37 @@
# SSHKeyVault – Konfiguration. Kopieren nach .env und Werte eintragen.
# Alle eigenen Variablen tragen das Praefix SSHKV_ (Ausnahme: DATABASE_URL fuer Prisma).
# --- Datenbank -----------------------------------------------------------------
DATABASE_URL=postgresql://sshkv:sshkv@localhost:5436/sshkv?schema=public
# Nur fuer Tests (wird von Vitest geleert!)
TEST_DATABASE_URL=postgresql://sshkv:sshkv@localhost:5436/sshkv_test?schema=public
# --- Web -----------------------------------------------------------------------
# Oeffentliche Adresse (hinter dem Reverse Proxy). Bestimmt Secure-Cookie und OIDC-Rueckruf.
SSHKV_APP_URL=http://localhost:3400
# Signiert Sitzungen. Erzeugen mit: openssl rand -base64 48
SSHKV_APP_SECRET=
# --- Master-Key (nur Worker!) --------------------------------------------------
# Datei mit 32 zufaelligen Bytes. Erzeugen mit: head -c 32 /dev/urandom > master.key
# Ohne diese Datei ist jedes Datenbank-Backup wertlos – getrennt sichern!
SSHKV_MASTER_KEY_FILE=./geheim/master.key
# --- OIDC (authentik) ----------------------------------------------------------
SSHKV_OIDC_ISSUER=
SSHKV_OIDC_CLIENT_ID=
SSHKV_OIDC_CLIENT_SECRET=
SSHKV_OIDC_NAME=authentik
# Gruppen im Anbieter, die auf Rollen abgebildet werden
SSHKV_OIDC_GROUP_ADMIN=sshkv-admins
SSHKV_OIDC_GROUP_OPERATOR=sshkv-operators
SSHKV_OIDC_GROUP_READER=sshkv-readers
# --- Notfall-Admin -------------------------------------------------------------
# Wird beim ersten Seed angelegt. TOTP wird bei der ersten Anmeldung eingerichtet.
SSHKV_BREAKGLASS_USER=notfall
SSHKV_BREAKGLASS_PASSWORD=
# --- Worker --------------------------------------------------------------------
# Zeitplan fuer den Abgleich aller Hosts (cron)
SSHKV_SYNC_CRON=30 2 * * *
+1
View File
@@ -0,0 +1 @@
pnpm exec commitlint --edit "$1"
+1
View File
@@ -0,0 +1 @@
pnpm lint-staged
+8
View File
@@ -0,0 +1,8 @@
.next
node_modules
coverage
docs
*.md
pnpm-lock.yaml
src/generated
prisma/migrations
+6
View File
@@ -0,0 +1,6 @@
{
"semi": true,
"singleQuote": false,
"trailingComma": "all",
"printWidth": 110
}
+100
View File
@@ -0,0 +1,100 @@
# Konventionen für SSHKeyVault (`sshkv`)
Diese Datei ist verbindlich. Plan und Begründungen: `docs/PLAN.md`. Arbeitsweise wie in
Laufzettel und ProxLedger.
## Arbeitsweise
- Issues liegen als Dateien in `docs/issues/NNN-slug.md` und sind die Quelle der Wahrheit;
`pnpm issues:sync` überträgt sie nach Gitea (#030).
- Je Issue ein Branch `phase/NNN-slug`, Merge nach `main` mit `--no-ff`.
- Conventional Commits auf Deutsch, Betreff ≤ 72 Zeichen, Umlaute im Betreff gefaltet,
`(#NNN)` am Ende. Der Body erklärt das **Warum**.
- `DEVLOG.md`: Zeit je Issue und Meilenstein, Sitzungen mit „Erledigt“ und „Entscheidungen“.
- `CHANGELOG.md`: Keep a Changelog, deutsch. Tag `vX.Y.Z` je Meilenstein; Version in `package.json`.
## Sprache
**Deutsch, überall wo ein Mensch liest oder benennt** – auch Bezeichner (Funktionen, Variablen,
Prisma-Modelle und -Felder, Enum-Werte, Dateinamen, Tests). **Umlaute in Bezeichnern werden
ASCII-gefaltet** (`Schluessel`, `geaendertAm`). Oberflächentexte, Fehlermeldungen und
Protokoll-Aktionen schreiben sich richtig („Schlüssel widerrufen“).
Englisch bleiben Framework-Namen (`page.tsx`, `route.ts`, `GET`) und Fachjargon
(`Host`, `Fingerprint`, `Token`, `OIDC`, `authorized_keys`, `Public Key`, `Private Key`, `Drift`).
Kommentare erklären **warum**, nicht was, und tragen wo hilfreich die Issue-Nummer `(#007)`.
## Namensabbildung zum Plan
| Plan | Modell | Plan | Modell |
|---|---|---|---|
| User | `Benutzer` | PublicKeyUsage | `PublicVerwendung` |
| Person | `Person` | PrivateKeyUsage | `PrivateVerwendung` |
| Customer | `Kunde` | ManagedAccount | `Zielkonto` |
| Host | `Host` | Template | `Vorlage` |
| HostGroup | `Hostgruppe` | Observation | `Beobachtung` |
| HostKey | `HostSchluessel` | DriftFinding | `Abweichung` |
| SshKey | `Schluessel` | Operation | `Vorgang` |
| AuditEvent | `Protokoll` | Setting | `Einstellung` |
## Schichtung
```
src/server/services/* Regeln und Schreiboperationen.
src/server/queries/* Lesemodelle. Wenden die Rolle selbst an.
src/server/actions/* Dünne "use server"-Einstiege: Recht prüfen, zod, Service rufen.
src/server/auth/* Anmeldung, Sitzung, Rechte, Re-Auth.
src/lib/* Framework-freie Helfer (kein Next, kein React, kein Prisma) – gut testbar.
src/lib/ssh/* OpenSSH-Formate, authorized_keys-Parser, Fingerprints. Rein.
src/worker/* Alles, was SSH spricht oder den Master-Key braucht. Läuft nur im Worker.
src/components/* Darstellung, keine Geschäftslogik.
```
## Feste Regeln
- **Private Keys** erscheinen nie in Logs, Fehlermeldungen, Listenantworten, Props von
Client-Komponenten oder im Protokoll. Prisma-Abfragen auf `Schluessel` nutzen feste
`select`-Listen; `privatVerschluesselt` liest nur `src/worker/geheimnisse.ts`.
- **Master-Key nur im Worker.** Der Webprozess importiert nichts aus `src/worker/*`.
- **Kein Klartext-Passwort** (Bootstrap) in Datenbank, Job-Payload, Log oder Protokoll.
- **Host-Keys werden strikt geprüft.** Es gibt keinen Codepfad, der einen geänderten Host-Key
automatisch annimmt.
- **Rechte** nur serverseitig über `darf()`; jede Seite und jeder Route Handler prüft selbst.
Rolle wird pro Anfrage aus der Datenbank gelesen, nicht aus dem Token.
- **Protokoll** ist append-only (Trigger) und über eine Hash-Kette verbunden.
- Zod immer über `@/lib/validierung` importieren (deutsche Meldungen).
## Werkzeuge und Versionen
Node 24 (`.nvmrc`), pnpm 12. Exakt gepinnt: `next@16.3.6`, `react@19.3.0`,
`prisma`/`@prisma/client`/`@prisma/adapter-pg` `7.10.0`, `vitest@5.0.1`, `vite@8.3.0`,
`@playwright/test@1.63.0`. `eslint` bleibt auf 9.x, `typescript` auf `~6.0.3`
(Gründe siehe Laufzettel/CLAUDE.md).
**Next 16:** `middleware.ts` heißt `proxy.ts`. `cookies()`, `headers()`, `params`,
`searchParams` sind async.
**Prisma 7:** Generator `prisma-client` nach `src/generated/prisma`, Zugriff über
`@prisma/adapter-pg`, Lazy-Proxy in `src/lib/db.ts`. Typen aus `@/generated/prisma/client`,
Enum-Werte aus `@/generated/prisma/enums`.
## Entwicklung
```bash
pnpm db:up # PostgreSQL auf 127.0.0.1:5436
pnpm db:migrate && pnpm db:seed
pnpm dev # http://localhost:3400
pnpm worker # zweites Terminal
```
Oberflächen immer gegen den **gebauten Stand** prüfen (`pnpm build && pnpm start`) – siehe
Laufzettel: `next dev` hydriert in dieser Umgebung nicht zuverlässig.
## Vor jedem Commit
```bash
pnpm lint && pnpm typecheck && pnpm format:pruefen && pnpm test
```
Vor jedem Merge zusätzlich `pnpm test:integration` (braucht Docker).
+11
View File
@@ -0,0 +1,11 @@
// Conventional Commits. Betreff auf Deutsch, hoechstens 72 Zeichen (siehe CLAUDE.md).
const konfiguration = {
extends: ["@commitlint/config-conventional"],
rules: {
"header-max-length": [2, "always", 72],
"subject-case": [0],
"body-max-line-length": [0],
},
};
export default konfiguration;
+26
View File
@@ -0,0 +1,26 @@
# Nur fuer die Entwicklung: PostgreSQL auf eigenem Port (andere Projekte belegen
# 5433/5434). Diese Datei niemals im Betrieb verwenden.
name: sshkv-dev
services:
postgres:
image: postgres:17-alpine
container_name: sshkv-dev-db
restart: unless-stopped
environment:
POSTGRES_USER: sshkv
POSTGRES_PASSWORD: sshkv
POSTGRES_DB: sshkv
ports:
- "127.0.0.1:${DEV_DB_PORT:-5436}:5432"
volumes:
- datenbank-dev:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U sshkv"]
interval: 5s
timeout: 5s
retries: 10
volumes:
datenbank-dev:
+12 -9
View File
@@ -1,24 +1,27 @@
# #001 Projekt-Setup
**Status:** offen
**Status:** erledigt
**Meilenstein:** v0.1.0
**Labels:** infrastruktur
**Geschätzt:** 3 h **Tatsächlich:** –
**Geschätzt:** 3 h **Tatsächlich:** 0 h 15 min
## Ziel
Lauffähiges Grundgerüst: Next.js 16, TypeScript strict, Prisma 7, pnpm, ESLint/Prettier/Vitest, Commit-Prüfung, Docker-Compose für die Entwicklungsdatenbank.
## Aufgaben
- [ ] Next.js 16 (App Router), React 19, TypeScript strict
- [ ] pnpm 12, gepinnte Versionen, `.nvmrc`
- [ ] ESLint 9, Prettier, Vitest, commitlint + husky
- [ ] `docker-compose.dev.yml` mit PostgreSQL
- [ ] `.env.example` mit Präfix `SSHKV_`
- [ ] `CLAUDE.md` mit Konventionen
- [x] Next.js 16 (App Router), React 19, TypeScript strict
- [x] pnpm 12, gepinnte Versionen, `.nvmrc`
- [x] ESLint 9, Prettier, Vitest, commitlint + husky
- [x] `docker-compose.dev.yml` mit PostgreSQL
- [x] `.env.example` mit Präfix `SSHKV_`
- [x] `CLAUDE.md` mit Konventionen
## Akzeptanzkriterien
- [ ] `pnpm lint && pnpm typecheck && pnpm test && pnpm build` grün
- [x] `pnpm lint && pnpm typecheck && pnpm test && pnpm build` grün
## Abhängigkeiten
–
## Notizen / Entscheidungen
- Konfiguration (tsconfig, ESLint, Prettier, commitlint, husky) aus Laufzettel übernommen.
- Entwicklungsdatenbank auf Port 5436 (5433/5434 belegen ProxLedger und Laufzettel), Dev-Server auf 3400.
- `ssh2` ohne native Build-Skripte (`allowBuilds: false`): die reine JS-Fassung reicht, und es muss kein Compiler ins Abbild.
+45
View File
@@ -0,0 +1,45 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
import prettier from "eslint-config-prettier/flat";
export default defineConfig([
...nextVitals,
...nextTs,
prettier,
{
rules: {
// Zod wird in src/lib/validierung.ts auf deutsche Meldungen eingestellt.
// Ein direkter Import aus "zod" umginge das, und die englischen
// Standardsaetze landeten in der Oberflaeche.
"no-restricted-imports": [
"error",
{
paths: [
{
name: "zod",
message:
'Bitte `import { z } from "@/lib/validierung"` benutzen – dort sind die deutschen Meldungen eingestellt.',
},
],
},
],
},
},
{
// Die Datei, die Zod einstellt, muss es selbst importieren duerfen.
files: ["src/lib/validierung.ts"],
rules: { "no-restricted-imports": "off" },
},
globalIgnores([
".next/**",
"out/**",
"build/**",
"coverage/**",
"next-env.d.ts",
// Erzeugter Prisma-Client.
"src/generated/**",
"test-results/**",
"playwright-report/**",
]),
]);
+29
View File
@@ -0,0 +1,29 @@
import type { NextConfig } from "next";
/**
* Sicherheitskopfzeilen fuer alle Antworten. Die CSP selbst wird pro Anfrage in
* src/proxy.ts mit einer Nonce gesetzt, weil sie dort dynamisch sein muss.
*/
const SICHERHEITS_KOPFZEILEN = [
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
// sshkv laeuft im internen Netz; Pfade wie /schluessel/<id> sollen trotzdem nie
// als Referrer an fremde Seiten gehen.
{ key: "Referrer-Policy", value: "no-referrer" },
{ key: "Permissions-Policy", value: "geolocation=(), microphone=(), camera=()" },
{ key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains" },
{ key: "Cross-Origin-Opener-Policy", value: "same-origin" },
];
const konfiguration: NextConfig = {
// Fuer das schlanke Laufzeit-Abbild im Dockerfile.
output: "standalone",
poweredByHeader: false,
// ssh2 bringt optionale native Teile mit, die nicht gebuendelt werden duerfen.
serverExternalPackages: ["ssh2", "@node-rs/argon2", "pg-boss"],
async headers() {
return [{ source: "/:pfad*", headers: SICHERHEITS_KOPFZEILEN }];
},
};
export default konfiguration;
+90
View File
@@ -0,0 +1,90 @@
{
"name": "sshkv",
"version": "0.0.0",
"private": true,
"type": "module",
"description": "SSHKeyVault – zentrale Verwaltung von SSH-Schluesseln mit Weboberflaeche",
"packageManager": "pnpm@12.6.0",
"engines": {
"node": ">=24"
},
"scripts": {
"dev": "next dev -p 3400",
"build": "next build",
"build:worker": "esbuild src/worker/index.ts --bundle --platform=node --target=node24 --format=esm --outfile=dist/worker.mjs --banner:js=\"import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);\" --external:@prisma/client --external:@node-rs/argon2 --external:pg-native --external:ssh2 --external:cpu-features",
"start": "next start -p 3400",
"worker": "tsx src/worker/index.ts",
"lint": "eslint",
"typecheck": "tsc --noEmit",
"format": "prettier --write .",
"format:pruefen": "prettier --check .",
"test": "vitest run",
"test:watch": "vitest",
"test:integration": "vitest run --config vitest.integration.config.mts",
"db:up": "docker compose -f docker-compose.dev.yml up -d --wait",
"db:down": "docker compose -f docker-compose.dev.yml down",
"db:migrate": "prisma migrate dev",
"db:seed": "prisma db seed",
"db:generate": "prisma generate",
"issues:sync": "tsx scripts/gitea-issues.ts",
"icons": "node scripts/icons.mjs",
"postinstall": "prisma generate",
"prepare": "husky"
},
"lint-staged": {
"*.{ts,tsx,mts,mjs}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,yml,yaml}": [
"prettier --write"
]
},
"dependencies": {
"@fontsource/ibm-plex-sans": "^5.3.0",
"@fontsource/jetbrains-mono": "^5.3.0",
"@fontsource/space-grotesk": "^5.3.0",
"@node-rs/argon2": "^2.2.2",
"@prisma/adapter-pg": "7.10.0",
"@prisma/client": "7.10.0",
"clsx": "^2.1.1",
"jose": "^6.2.12",
"lucide-react": "1.47.0",
"next": "16.3.6",
"openid-client": "^6.8.8",
"pg": "^8.23.1",
"pg-boss": "^12.37.1",
"qrcode": "^1.5.4",
"react": "19.3.0",
"react-dom": "19.3.0",
"ssh2": "^1.17.0",
"zod": "^4.6.5"
},
"devDependencies": {
"@commitlint/cli": "^21.2.3",
"@commitlint/config-conventional": "^21.2.3",
"@playwright/test": "1.63.0",
"@tailwindcss/postcss": "^4.3.3",
"@types/node": "^24.19.2",
"@types/pg": "^8.23.1",
"@types/qrcode": "^1.5.6",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@types/ssh2": "^1.15.6",
"dotenv": "^18.0.7",
"esbuild": "^0.28.2",
"eslint": "^9.39.5",
"eslint-config-next": "16.3.6",
"eslint-config-prettier": "^10.1.8",
"husky": "^9.1.7",
"lint-staged": "^17.6.0",
"prettier": "^3.9.10",
"prisma": "7.10.0",
"sharp": "^0.35.5",
"tailwindcss": "^4.3.3",
"tsx": "^4.23.15",
"typescript": "~6.0.3",
"vite": "8.3.0",
"vitest": "5.0.1"
}
}
+7534
View File
File diff suppressed because it is too large Load Diff
+9
View File
@@ -0,0 +1,9 @@
# pnpm 12 fuehrt Build-Skripte von Abhaengigkeiten nur nach ausdruecklicher Freigabe aus.
allowBuilds:
"@prisma/engines": true
esbuild: true
prisma: true
unrs-resolver: true
sharp: true
ssh2: false
cpu-features: false
+6
View File
@@ -0,0 +1,6 @@
// Tailwind 4 wird ueber sein PostCSS-Plugin eingebunden; eine tailwind.config.js gibt es nicht mehr.
const konfiguration = {
plugins: { "@tailwindcss/postcss": {} },
};
export default konfiguration;
+11
View File
@@ -0,0 +1,11 @@
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
seed: "tsx prisma/seed.ts",
},
datasource: { url: process.env["DATABASE_URL"] },
});
+16
View File
@@ -0,0 +1,16 @@
// SSHKeyVault – Datenmodell. Die vollstaendigen Modelle entstehen in #003.
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "postgresql"
}
model Einstellung {
schluessel String @id
wert Json
geaendertAm DateTime @updatedAt
}
+7
View File
@@ -0,0 +1,7 @@
import { NextResponse } from "next/server";
import { VERSION } from "@/lib/version";
/** Healthcheck fuer Docker und Reverse Proxy. Verraet nichts ausser der Version. */
export function GET() {
return NextResponse.json({ status: "ok", version: VERSION });
}
+1
View File
@@ -0,0 +1 @@
@import "tailwindcss";
+16
View File
@@ -0,0 +1,16 @@
import type { Metadata } from "next";
import type { ReactNode } from "react";
import "./globals.css";
export const metadata: Metadata = {
title: { default: "SSHKeyVault", template: "%s · SSHKeyVault" },
description: "Zentrale Verwaltung von SSH-Schlüsseln",
};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="de">
<body>{children}</body>
</html>
);
}
+3
View File
@@ -0,0 +1,3 @@
export default function Startseite() {
return <main>SSHKeyVault</main>;
}
+39
View File
@@ -0,0 +1,39 @@
import { PrismaPg } from "@prisma/adapter-pg";
import { PrismaClient } from "@/generated/prisma/client";
/**
* Zugriff auf die Datenbank.
*
* Der Client wird erst beim ersten Zugriff gebaut. Dadurch laeuft `next build`
* ohne gesetzte DATABASE_URL durch, obwohl Module den Import auf oberster Ebene
* haben. In der Entwicklung liegt die Instanz an globalThis, damit Hot Reload
* nicht bei jedem Speichern eine neue Verbindung aufbaut.
*/
function clientBauen(): PrismaClient {
const url = process.env["DATABASE_URL"];
if (!url) {
throw new Error("DATABASE_URL ist nicht gesetzt. Siehe .env.example.");
}
return new PrismaClient({ adapter: new PrismaPg({ connectionString: url }) });
}
const globalerZwischenspeicher = globalThis as typeof globalThis & {
sshkvPrisma?: PrismaClient;
};
let instanz: PrismaClient | undefined;
function instanzHolen(): PrismaClient {
if (process.env["NODE_ENV"] !== "production") {
globalerZwischenspeicher.sshkvPrisma ??= clientBauen();
return globalerZwischenspeicher.sshkvPrisma;
}
instanz ??= clientBauen();
return instanz;
}
export const prisma = new Proxy({} as PrismaClient, {
get(_ziel, eigenschaft, empfaenger) {
return Reflect.get(instanzHolen(), eigenschaft, empfaenger);
},
});
+23
View File
@@ -0,0 +1,23 @@
import { z } from "zod";
/**
* Zod mit deutschen Standardmeldungen (#001, uebernommen aus Laufzettel).
*
* Ohne diese Einstellung kommen bei fehlenden oder falsch typisierten Feldern
* englische Saetze heraus („Invalid input: expected string“), und die landen
* ueber die Server Actions unmittelbar in der Oberflaeche. Eigene `message`-Texte
* helfen nur dort, wo man an sie gedacht hat — die Lokalisierung deckt auch alles
* ab, woran niemand gedacht hat.
*
* Deshalb wird `z` **hier** eingestellt und von hier weitergereicht: ein Import
* direkt aus "zod" umginge die Einstellung. Die ESLint-Regel
* `no-restricted-imports` sorgt dafuer, dass das auffaellt.
*/
z.config(z.locales.de());
export { z };
// Was sonst noch aus Zod gebraucht wird, laeuft ebenfalls ueber diesen Pfad –
// sonst muesste doch wieder jemand direkt aus "zod" importieren.
export { ZodError } from "zod";
export type { ZodIssue, ZodType } from "zod";
+6
View File
@@ -0,0 +1,6 @@
import paket from "../../package.json" with { type: "json" };
/** Version aus package.json – erscheint in der Seitenleiste und im Healthcheck. */
export const VERSION: string = paket.version;
export const KURZNAME = "sshkv";
export const PRODUKTNAME = "SSHKeyVault";
+7
View File
@@ -0,0 +1,7 @@
import "dotenv/config";
// Tests laufen nie gegen die Entwicklungsdatenbank: TEST_DATABASE_URL ersetzt DATABASE_URL.
if (process.env["TEST_DATABASE_URL"]) {
process.env["DATABASE_URL"] = process.env["TEST_DATABASE_URL"];
}
process.env["SSHKV_APP_SECRET"] ??= "test-geheimnis-test-geheimnis-test-geheimnis";
+12
View File
@@ -0,0 +1,12 @@
import { describe, expect, it } from "vitest";
import { KURZNAME, PRODUKTNAME, VERSION } from "@/lib/version";
describe("Version", () => {
it("folgt SemVer", () => {
expect(VERSION).toMatch(/^\d+\.\d+\.\d+(-[\w.]+)?$/);
});
it("traegt die verbindlichen Namen", () => {
expect(PRODUKTNAME).toBe("SSHKeyVault");
expect(KURZNAME).toBe("sshkv");
});
});
+38
View File
@@ -0,0 +1,38 @@
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"verbatimModuleSyntax": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.mts",
".next/types/**/*.ts",
".next/dev/types/**/*.ts"
],
"exclude": ["node_modules"]
}
+16
View File
@@ -0,0 +1,16 @@
import { fileURLToPath } from "node:url";
import { defineConfig } from "vitest/config";
export default defineConfig({
resolve: {
alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) },
},
test: {
include: ["tests/**/*.test.ts", "tests/**/*.test.tsx", "src/**/*.test.ts"],
exclude: ["tests/integration/**", "node_modules/**"],
environment: "node",
setupFiles: ["tests/setup-env.ts"],
// Datenbanktests teilen sich eine Datenbank und duerfen sich nicht ueberholen.
fileParallelism: false,
},
});
+17
View File
@@ -0,0 +1,17 @@
import { fileURLToPath } from "node:url";
import { defineConfig } from "vitest/config";
/** Integrationstests gegen echte SSH-Ziele in Containern (#011). */
export default defineConfig({
resolve: {
alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) },
},
test: {
include: ["tests/integration/**/*.test.ts"],
environment: "node",
setupFiles: ["tests/setup-env.ts"],
fileParallelism: false,
testTimeout: 60_000,
hookTimeout: 120_000,
},
});