chore: initial project setup

- Git repository mit .gitignore und .editorconfig
- NPM-Projekt mit package.json und Dependencies
- Projekt-Struktur (src/, docs/, examples/, test/)
- Umfassende README.md mit Features und Roadmap
- Architektur-Dokumentation mit Mermaid-Diagrammen
- Design-Entscheidungen dokumentiert
- .env.example für Forgejo API-Konfiguration
- MIT Lizenz und Contributing Guidelines

Status: Phase 1 - Core Parser (Setup abgeschlossen)
This commit is contained in:
Jörg Lohrer 2025-10-01 15:28:30 +02:00
commit fbd6630f6d
9 changed files with 1263 additions and 0 deletions

435
docs/ARCHITECTURE.md Normal file
View file

@ -0,0 +1,435 @@
# 🏛️ Architektur-Dokumentation
## Überblick
MDParser ist ein modularer, erweiterbarer Parser für Markdown-Dateien mit YAML Front Matter, optimiert für die Verarbeitung von AMB-konformen Bildungsressourcen.
## 📐 Architektur-Diagramm
```mermaid
flowchart TB
subgraph "Datenquellen"
File["📄 Lokale Datei"]
URL["🌐 HTTP/HTTPS URL"]
API["🔌 Forgejo/Gitea API"]
end
subgraph "Core Parser"
Fetch["Fetch Module<br/>Daten abrufen"]
Unified["unified Pipeline<br/>remark-parse<br/>remark-frontmatter<br/>remark-gfm"]
YAMLParser["YAML Parser<br/>yaml library"]
end
subgraph "Extraction Layer"
FrontMatter["Front Matter<br/>Extractor"]
AMBExtract["AMB Metadata<br/>Extractor<br/>(schema.org)"]
ContentExtract["Content<br/>Extractor<br/>(AST)"]
end
subgraph "Output Formats"
JSON["📦 JSON<br/>Structured Data"]
AST["🌲 MDAST<br/>Abstract Syntax Tree"]
HTML["📝 HTML<br/>(optional)"]
end
subgraph "Transformers (Phase 2)"
WP["WordPress<br/>REST API v2"]
Nostr["Nostr<br/>NIP-23"]
end
File --> Fetch
URL --> Fetch
API --> Fetch
Fetch --> Unified
Unified --> YAMLParser
Unified --> FrontMatter
FrontMatter --> AMBExtract
FrontMatter --> ContentExtract
AMBExtract --> JSON
ContentExtract --> AST
ContentExtract --> HTML
JSON --> WP
JSON --> Nostr
AST --> WP
AST --> Nostr
style Unified fill:#e1f5ff,stroke:#01579b
style AMBExtract fill:#f3e5f5,stroke:#4a148c
style JSON fill:#e8f5e9,stroke:#1b5e20
```
## 🎯 Design-Prinzipien
### 1. **Modularität**
- Jede Komponente hat eine klare Verantwortung
- Lose Kopplung zwischen Modulen
- Einfach erweiterbar durch Plugin-System
### 2. **Isomorphie**
- Code funktioniert in Node.js **und** Browser
- Keine Node.js-spezifischen APIs im Core
- Native `fetch` für HTTP-Requests
### 3. **Standards-Konformität**
- AMB-Metadatenstandard (schema.org)
- MDAST (Markdown Abstract Syntax Tree)
- CommonMark + GFM (GitHub Flavored Markdown)
### 4. **Fehlertoleranz**
- Graceful Degradation bei fehlenden Metadaten
- Validierung mit aussagekräftigen Fehlermeldungen
- Optionale Felder werden sauber behandelt
## 📦 Modul-Struktur
### Core Module
#### 1. **Parser (`src/parser.js`)**
```javascript
export async function parseMarkdownFile(filePath, options) {
// Haupteinstiegspunkt für Markdown-Parsing
// Orchestriert unified Pipeline
return {
yaml: {}, // Rohes YAML Front Matter
metadata: {}, // Extrahierte AMB-Metadaten
ast: {}, // Markdown AST
content: "", // Reiner Content
html: "" // Optional: HTML-Output
}
}
```
**Technologie:** unified + remark Ökosystem
**Plugins:**
- `remark-parse` - Markdown → AST
- `remark-frontmatter` - YAML Front Matter Support
- `remark-gfm` - GitHub Flavored Markdown
- `remark-stringify` - AST → Markdown (optional)
- `remark-html` - AST → HTML (optional)
#### 2. **Forgejo Client (`src/forgejo-client.js`)**
```javascript
export class ForgejoClient {
constructor(config) { /* ... */ }
async getFileContent(path) { /* ... */ }
async listDirectory(path) { /* ... */ }
async listPosts(postsDir) { /* ... */ }
async getRepository() { /* ... */ }
}
```
**API-Endpoints:**
- `/repos/{owner}/{repo}/contents/{path}` - Dateiinhalt
- `/repos/{owner}/{repo}/git/trees/{sha}` - Verzeichnis-Listing
- Content wird Base64-dekodiert
#### 3. **YAML Extractor (`src/extractors/yaml-extractor.js`)**
```javascript
export function extractYAML(markdownContent) {
// Extrahiert YAML Front Matter
// Parst mit yaml library
return yamlObject
}
```
**Technologie:** `yaml` library (v2.x)
**Features:**
- Komplexe YAML-Strukturen
- Arrays, nested Objects
- Multi-line Strings
- Datum-Parsing
#### 4. **AMB Metadata Extractor (`src/extractors/amb-extractor.js`)**
```javascript
export function extractAMBMetadata(yamlObject) {
// Transformiert YAML → Schema.org
// Validiert AMB-Konformität
return ambMetadata
}
```
**Mapping:**
```javascript
{
"@context": "https://schema.org/",
"type": "LearningResource",
"name": yaml.commonMetadata.name,
"description": yaml.commonMetadata.description,
"creator": mapCreators(yaml.commonMetadata.creator),
"license": yaml.commonMetadata.license,
"inLanguage": yaml.commonMetadata.inLanguage,
"datePublished": yaml.commonMetadata.datePublished,
"about": yaml.commonMetadata.about,
"image": yaml.commonMetadata.image,
"id": yaml.commonMetadata.id,
"learningResourceType": yaml.commonMetadata.learningResourceType,
"educationalLevel": yaml.commonMetadata.educationalLevel
}
```
### Transformation Layer (Phase 2)
#### 5. **WordPress Transformer (`src/transformers/wordpress.js`)**
```javascript
export function transformToWordPress(parsedData) {
return {
title: "",
content: "",
excerpt: "",
featured_media: 0,
tags: [],
categories: [],
meta: {},
author: 0
}
}
```
**WordPress REST API v2 Format**
#### 6. **Nostr Transformer (`src/transformers/nostr.js`)**
```javascript
export function transformToNostr(parsedData) {
return {
kind: 30023, // NIP-23 Long-form
tags: [
["d", ""], // unique identifier
["title", ""],
["summary", ""],
["published_at", ""],
["image", ""],
["t", ""], // hashtags
["e", ""], // event refs
["a", ""], // article refs
["p", ""] // pubkey refs
],
content: "" // Markdown content
}
}
```
## 🔄 Datenfluss
### 1. Parsing-Pipeline
```mermaid
sequenceDiagram
participant Client
participant Parser
participant Unified
participant YAML
participant AMB
Client->>Parser: parseMarkdownFile(path)
Parser->>Unified: process(markdown)
Unified->>YAML: extract front matter
YAML-->>Parser: yamlObject
Parser->>AMB: extractAMBMetadata(yaml)
AMB-->>Parser: ambMetadata
Unified-->>Parser: ast
Parser-->>Client: { yaml, metadata, ast, content }
```
### 2. Forgejo API Integration
```mermaid
sequenceDiagram
participant Client
participant ForgejoClient
participant API as Forgejo API
participant Parser
Client->>ForgejoClient: getFileContent(path)
ForgejoClient->>API: GET /repos/.../contents/...
API-->>ForgejoClient: { content: base64, ... }
ForgejoClient->>ForgejoClient: decode base64
ForgejoClient-->>Client: markdown string
Client->>Parser: parseMarkdownFile(markdown)
Parser-->>Client: parsed data
```
### 3. Transformation (Phase 2)
```mermaid
flowchart LR
Parse["Parsed Data<br/>{yaml, metadata, ast}"]
WPT["WordPress<br/>Transformer"]
NostrT["Nostr<br/>Transformer"]
WPAPI["WordPress<br/>REST API"]
NostrRelay["Nostr<br/>Relay"]
Parse --> WPT
Parse --> NostrT
WPT --> WPAPI
NostrT --> NostrRelay
style Parse fill:#e8f5e9
style WPT fill:#fff3e0
style NostrT fill:#f3e5f5
```
## 🛠️ Technologie-Entscheidungen
### Warum unified/remark?
| Alternative | Pro | Contra | Entscheidung |
|-------------|-----|--------|--------------|
| **marked** | ✅ Sehr populär<br/>✅ Einfach | ❌ HTML-fokussiert<br/>❌ Kein AST | ❌ Abgelehnt |
| **markdown-it** | ✅ Erweiterbar<br/>✅ Performance | ❌ Komplexe API<br/>❌ HTML-fokussiert | ❌ Abgelehnt |
| **unified/remark** | ✅ AST-basiert<br/>✅ Isomorph<br/>✅ Plugin-System<br/>✅ Standard | ⚠️ Lernkurve | ✅ **GEWÄHLT** |
| **gray-matter + marked** | ✅ Einfach | ❌ Weniger strukturiert | ⚠️ Fallback |
### Warum `yaml` library?
| Alternative | Pro | Contra | Entscheidung |
|-------------|-----|--------|--------------|
| **js-yaml** | ✅ Populär | ❌ Größere Bundle-Size | ❌ Abgelehnt |
| **yaml** | ✅ Modern<br/>✅ Spec-compliant<br/>✅ Klein | - | ✅ **GEWÄHLT** |
| JSON.parse | ✅ Native | ❌ Kein YAML-Support | ❌ Nicht geeignet |
### Warum native `fetch`?
- ✅ Standard in Node.js 18+
- ✅ Identische API im Browser
- ✅ Keine Dependencies
- ✅ Async/await Support
## 📊 Performance-Überlegungen
### Caching-Strategie
```javascript
// Optional: Cache für häufig abgerufene Dateien
const cache = new Map()
async function parseWithCache(path, options) {
const cacheKey = `${path}-${JSON.stringify(options)}`
if (cache.has(cacheKey)) {
return cache.get(cacheKey)
}
const result = await parseMarkdownFile(path, options)
cache.set(cacheKey, result)
return result
}
```
### Rate Limiting für APIs
```javascript
// Forgejo API: Max. 10 Requests/Sekunde
const rateLimiter = new RateLimiter({
tokensPerInterval: 10,
interval: 1000
})
```
## 🔒 Sicherheit
### Input-Validierung
- YAML-Bombing-Schutz (max. depth/size)
- Path-Traversal-Schutz bei Dateizugriffen
- Content-Type-Validierung bei API-Requests
### Sanitization
- XSS-Schutz bei HTML-Output (optional mit DOMPurify)
- SQL-Injection-Schutz bei DB-Integration (Phase 2)
## 🧪 Testing-Strategie
### Unit Tests
```
test/
├── parser.test.js
├── yaml-extractor.test.js
├── amb-extractor.test.js
├── forgejo-client.test.js
└── transformers/
├── wordpress.test.js
└── nostr.test.js
```
### Integration Tests
- End-to-End mit echtem Forgejo-Repository
- Mocking der API-Responses
### Test-Fixtures
```
test/fixtures/
├── valid-amb.md
├── missing-metadata.md
├── complex-yaml.md
└── github-flavored.md
```
## 🚀 Deployment-Szenarien
### 1. **Node.js CLI**
```bash
npm install -g mdparser
mdparser parse ./content/post.md
```
### 2. **Node.js Library**
```javascript
import { parseMarkdownFile } from 'mdparser'
const result = await parseMarkdownFile('./post.md')
```
### 3. **Browser (ESM)**
```html
<script type="module">
import { parseMarkdownFile } from './mdparser.js'
// ...
</script>
```
### 4. **Serverless Function**
```javascript
// Vercel/Netlify Function
export default async function handler(req, res) {
const result = await parseMarkdownFile(req.body.url)
res.json(result)
}
```
## 📈 Roadmap & Erweiterungen
### Phase 1: Core Parser ✅ (aktuell)
- [x] Projekt-Setup
- [ ] Parser-Implementierung
- [ ] Forgejo-Client
- [ ] AMB-Extraktor
- [ ] Tests & Dokumentation
### Phase 2: Transformers 🚧
- [ ] WordPress-Integration
- [ ] Nostr-Integration
- [ ] Batch-Processing
### Phase 3: Advanced Features 🔮
- [ ] Browser-Build
- [ ] CLI-Tool
- [ ] Webhook-Support
- [ ] Real-time Sync
- [ ] GraphQL-API
## 🤝 Contribution Guidelines
Siehe [CONTRIBUTING.md](../CONTRIBUTING.md) für Details zu:
- Code-Style (ESLint + Prettier)
- Commit-Conventions
- Pull-Request-Prozess
- Testing-Requirements

344
docs/DECISIONS.md Normal file
View file

@ -0,0 +1,344 @@
# 🎯 Design-Entscheidungen
Dieses Dokument dokumentiert wichtige technische Entscheidungen und deren Begründungen.
## 1. Parser-Technologie: unified/remark
**Entscheidung:** unified + remark als Core-Parser
**Datum:** 2025-10-01
### Kontext
Es wurden mehrere Markdown-Parser für die Verarbeitung von Bildungsressourcen mit YAML Front Matter evaluiert.
### Alternativen
| Parser | Vorteile | Nachteile | Score |
|--------|----------|-----------|-------|
| **marked** | Sehr populär (30k+ GitHub Stars)<br/>Einfache API<br/>Gute Performance | Fokus auf HTML-Output<br/>Kein AST-Zugriff<br/>Begrenzte Erweiterbarkeit | 6/10 |
| **markdown-it** | Erweiterbar durch Plugins<br/>Sehr schnell<br/>CommonMark-konform | Komplexe API<br/>HTML-fokussiert<br/>Keine Browser-Isomorphie | 7/10 |
| **unified/remark** | AST-basiert (MDAST)<br/>Isomorph (Node + Browser)<br/>Riesiges Plugin-Ökosystem<br/>De-facto Standard | Steilere Lernkurve<br/>Mehr Setup-Code | **9/10** |
| **gray-matter + marked** | Sehr einfach<br/>Schneller Einstieg | Weniger strukturiert<br/>Begrenzte Flexibilität | 5/10 |
### Entscheidungskriterien
1. **AST-Zugriff** (Gewichtung: 30%)
- ✅ unified: Voller MDAST-Zugriff für präzise Manipulation
- ❌ marked: Kein AST, nur Token-Stream
2. **Isomorphie** (Gewichtung: 25%)
- ✅ unified: Funktioniert identisch in Node.js und Browser
- ⚠️ markdown-it: Primär Node.js-fokussiert
3. **Erweiterbarkeit** (Gewichtung: 20%)
- ✅ unified: 200+ Plugins im Ökosystem
- ⚠️ marked: Begrenzte Extension-API
4. **Standards** (Gewichtung: 15%)
- ✅ unified: MDAST ist De-facto-Standard
- ✅ markdown-it: CommonMark-konform
5. **Community & Wartung** (Gewichtung: 10%)
- ✅ unified: Sehr aktiv, Teil von unifiedjs
- ✅ marked: Aktiv maintained
### Konsequenzen
**Positiv:**
- Maximale Flexibilität für zukünftige Features
- AST-Transformationen für WordPress/Nostr
- Browser-Support ohne Code-Änderungen
- Plugin-System für Custom-Syntax
**Negativ:**
- Mehr initialer Setup-Code erforderlich
- Höhere Komplexität für einfache Use-Cases
- Größere Learning-Curve für Contributors
### Code-Beispiel
```javascript
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkFrontmatter from 'remark-frontmatter'
import remarkGfm from 'remark-gfm'
const processor = unified()
.use(remarkParse)
.use(remarkFrontmatter, ['yaml'])
.use(remarkGfm)
const ast = processor.parse(markdown)
```
---
## 2. YAML-Parser: `yaml` library
**Entscheidung:** `yaml` (v2.x) statt `js-yaml`
**Datum:** 2025-10-01
### Kontext
YAML Front Matter muss zuverlässig geparst werden, inkl. komplexer Strukturen (Arrays, nested Objects).
### Alternativen
| Library | Bundle Size | Spec-Konformität | Performance | Score |
|---------|-------------|------------------|-------------|-------|
| **yaml** | 42 KB | YAML 1.2 | Sehr gut | **9/10** |
| **js-yaml** | 68 KB | YAML 1.2 | Gut | 7/10 |
| JSON.parse | 0 KB (native) | N/A (nur JSON) | Exzellent | 3/10 |
### Entscheidung: `yaml`
**Gründe:**
1. ✅ **Kleinere Bundle-Size** (42 KB vs. 68 KB)
2. ✅ **Moderne API** (ESM-first, TypeScript-Typen)
3. ✅ **Spec-compliant** (YAML 1.2)
4. ✅ **Aktive Entwicklung**
5. ✅ **Bessere Error-Messages**
### Konsequenzen
- Zuverlässiges Parsing komplexer YAML-Strukturen
- Gute Performance auch bei großen Dateien
- Kleinere Bundle-Size für Browser-Builds
---
## 3. HTTP-Client: Native `fetch`
**Entscheidung:** Native `fetch` API statt axios/node-fetch
**Datum:** 2025-10-01
### Kontext
HTTP-Requests für Forgejo API und URL-basierte Markdown-Dateien.
### Alternativen
| Option | Vorteile | Nachteile | Score |
|--------|----------|-----------|-------|
| **native fetch** | In Node 18+ integriert<br/>Identisch im Browser<br/>Keine Dependencies | Erfordert Node.js 18+ | **10/10** |
| **axios** | Feature-reich<br/>Interceptors<br/>Auto-Transform | Zusätzliche Dependency<br/>25 KB Bundle | 6/10 |
| **node-fetch** | Populär | Zusätzliche Dependency<br/>Nicht identisch mit Browser | 5/10 |
### Entscheidung: Native `fetch`
**Gründe:**
1. ✅ **Zero Dependencies**
2. ✅ **Isomorphie** - Identische API in Node.js 18+ und Browser
3. ✅ **Standard** - Web API Standard
4. ✅ **Async/await** - Moderne Syntax
**Requirement:** Node.js >= 18.0.0
### Konsequenzen
- Keine zusätzlichen Dependencies
- Code funktioniert ohne Änderungen im Browser
- Einfachere Maintenance
---
## 4. Module-System: ESM (ES Modules)
**Entscheidung:** Pure ESM, kein CommonJS
**Datum:** 2025-10-01
### Kontext
Wahl des Module-Systems für das Projekt.
### Entscheidung: ESM
```json
{
"type": "module"
}
```
**Gründe:**
1. ✅ **Standard** - ESM ist der JavaScript-Standard
2. ✅ **Browser-kompatibel** - Direkt in Browsern lauffähig
3. ✅ **Tree-Shaking** - Bessere Bundle-Optimierung
4. ✅ **Zukunftssicher** - CommonJS ist Legacy
**Import-Syntax:**
```javascript
import { parseMarkdownFile } from './parser.js'
// statt:
// const { parseMarkdownFile } = require('./parser.js')
```
### Konsequenzen
**Positiv:**
- Moderne, zukunftssichere Codebasis
- Bessere Bundle-Optimierung
- Browser-Kompatibilität
**Negativ:**
- Keine Kompatibilität mit alten CommonJS-only Tools
- Erfordert `.js` Extension bei relativen Imports
---
## 5. Datenquelle-Priorität: API-First
**Entscheidung:** Primärer Fokus auf Forgejo API
**Datum:** 2025-10-01
### Kontext
Verschiedene Datenquellen müssen unterstützt werden.
### Priorisierung
1. **Forgejo/Gitea API** (Priorität 1)
- Primäre Datenquelle
- Direkter API-Zugriff
- No Git-Clone nötig
2. **HTTP/HTTPS URLs** (Priorität 2)
- Einfacher Fallback
- Für öffentliche Dateien
3. **Lokale Dateien** (Priorität 3)
- Für Entwicklung und Tests
- Node.js-only
### Implementation
```javascript
// src/index.js
export async function parseMarkdown(source, options = {}) {
if (isForgejoURL(source)) {
return parseFromForgejo(source, options)
} else if (isHTTPURL(source)) {
return parseFromURL(source, options)
} else {
return parseFromFile(source, options)
}
}
```
---
## 6. Error-Handling: Graceful Degradation
**Entscheidung:** Fehlertolerante Metadaten-Extraktion
**Datum:** 2025-10-01
### Prinzip
Fehlende oder ungültige Metadaten führen nicht zu Fehler, sondern zu Warnings.
```javascript
{
metadata: {
name: "Unbekannt", // Fallback
_warnings: [
"Fehlendes Feld: commonMetadata.name"
]
}
}
```
**Gründe:**
1. ✅ Robustheit gegenüber unvollständigen Daten
2. ✅ Bessere User Experience
3. ✅ Ermöglicht partielle Verarbeitung
---
## 7. Browser-Build: Phase 2
**Entscheidung:** Browser-Build wird in Phase 2 implementiert
**Datum:** 2025-10-01
### Kontext
Browser-Unterstützung ist wichtig, aber nicht im ersten Release.
### Phasen-Plan
**Phase 1 (jetzt):**
- Node.js-kompatibel
- Isomorpher Code (aber kein Build)
- Tests in Node.js
**Phase 2:**
- ESM Browser-Build
- Bundling mit Rollup/esbuild
- Browser-Tests
**Grund:**
- Fokus auf Core-Funktionalität
- Vermeidung von Over-Engineering
- Schnelleres Initial-Release
---
## 8. Testing: Node.js Native Test Runner
**Entscheidung:** Node.js `--test` statt Jest/Mocha
**Datum:** 2025-10-01
### Alternativen
| Test-Framework | Vorteile | Nachteile | Score |
|----------------|----------|-----------|-------|
| **Node.js native** | Keine Dependencies<br/>Schnell<br/>ESM-Support | Weniger Features | **8/10** |
| **Jest** | Feature-reich<br/>Snapshots | Langsam<br/>ESM-Probleme | 6/10 |
| **Vitest** | Schnell<br/>ESM-first | Weitere Dependency | 7/10 |
### Entscheidung: Node.js Native
```javascript
// test/parser.test.js
import { test } from 'node:test'
import assert from 'node:assert'
test('parses markdown with YAML', async () => {
const result = await parseMarkdownFile('./fixtures/test.md')
assert.ok(result.yaml)
})
```
**Gründe:**
- ✅ Zero Dependencies für Tests
- ✅ Schnelle Ausführung
- ✅ Native ESM-Support
- ✅ Ausreichend für unsere Zwecke
---
## Zusammenfassung: Technologie-Stack
| Kategorie | Technologie | Begründung |
|-----------|-------------|------------|
| **Parser** | unified + remark | AST-basiert, isomorph, erweiterbar |
| **YAML** | yaml (v2.x) | Klein, modern, spec-compliant |
| **HTTP** | native fetch | Zero deps, isomorph |
| **Modules** | ESM | Standard, zukunftssicher |
| **Testing** | Node.js native | Zero deps, schnell |
| **Code Style** | Prettier + ESLint | Standard, automatisierbar |
---
## Änderungshistorie
| Datum | Entscheidung | Autor |
|-------|--------------|-------|
| 2025-10-01 | Initial: unified/remark | Jörg Lohrer |
| 2025-10-01 | YAML library | Jörg Lohrer |
| 2025-10-01 | Native fetch | Jörg Lohrer |
| 2025-10-01 | ESM-only | Jörg Lohrer |
---
**Fragen oder Vorschläge?** → [Issue erstellen](https://git.rpi-virtuell.de/Comenius-Institut/mdparser/issues)