validatePost akzeptiert jetzt auch string-dates im YYYY-MM-DD- oder
ISO-8601-format und coerced sie in ein Date-objekt in-place. vorher
schlug die validation für 13 von 18 altposts fehl, weil deren yaml
`date: "2023-02-26"` quoted war (hugo-konvention) und der yaml-parser
strings statt Date-instanzen liefert.
migration durchgelaufen (log in docs/publish-logs/): 18/18 success,
91 bilder auf beiden blossom-servern, 5 write-relays — bis auf
relay.damus.io, der bei 6 posts nicht auf OK antwortet (üblich bei
damus, rate-limiting). alle 7 multi-relay-posts haben weiter mindestens
4 acks (über MIN_RELAY_ACKS=2).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
processPost(args) orchestriert pro post:
1. frontmatter parsen + validieren
2. draft → skipped-draft
3. bilder sammeln + sequentiell zu blossom hochladen (mapping
dateiname → primary-url)
4. body mit rewriteImageUrls anpassen, coverUrl via resolveCoverUrl
5. kind:30023 event bauen via buildKind30023
6. checkExisting → action = new|update
7. signieren via nip-46
8. publishToRelays, prüfen ob minRelayAcks erreicht
alle externen abhängigkeiten (readPostFile, collectImages, upload,
sign, publish, checkExisting) via PostDeps-interface eingezogen
— einfach mockbar. fehler aller art landen als { status: failed,
error: msg }. 6 tests grün.
follow-up (nicht teil von task 15): license-tag und imeta-tags aus
images[]-frontmatter sind noch nicht im event. kommt in eigenem
folge-task, basierend auf der metadata-convention-spec.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
createLogger(opts) sammelt postSuccess/postFailed/postSkippedDraft-
events, druckt menschenlesbare zeilen (✓/✗/-), liefert am ende ein
RunLog mit allen einträgen plus start/end-timestamps. writeJson()
schreibt die komplette summary als json für archivierung/ci-artifact.
2 tests grün.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
filterPostDirs(lines, contentRoot) extrahiert post-verzeichnisse aus
git-diff-ausgabe (index.md-matches + asset-changes), ignoriert
_drafts/. contentRoot ist parameter (blaupausen-tauglich für nicht-
hugo-layouts). changedPostDirs(from, to, contentRoot, runner?) ruft
"git diff --name-only A..B" via dependency-injected runner. Plus
allPostDirs() für den --force-all-modus. 4 tests grün.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
uploadBlob(args) berechnet sha256, baut kind:24242-auth-event via
signer, schickt es base64-kodiert im authorization-header an PUT
/upload aller servers parallel. erfolg: report mit ok/failed-listen
und primaryUrl (erster erfolgreicher server). wirft wenn alle ablehnen.
BlossomClient via dependency-injection für tests.
TS-casts für Uint8Array→BufferSource/BodyInit (deno-strict). 3 tests
grün.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
collectImages(postDir) scannt ordner nach png/jpg/jpeg/gif/webp/svg,
ignoriert hugo-resize-derivate (*_hu_<hex>.*), liest bytes und gibt
sortierte ImageFile[]-liste zurück. mimeFromExt() mapped extension
auf mime-type. deno.jsonc test-task um --allow-write erweitert (Deno.
makeTempDir+writeFile in tests).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
publishToRelays(urls, ev, opts) publisht signiertes event parallel zu
allen relays, mit retries + exponential backoff + timeout pro versuch.
retour: { ok: string[], failed: string[] }. default-pool via
applesauce-relay 2.x (new RelayPool()); publishFn via dependency-
injection für tests. checkExisting(slug, pubkey, urls) fragt je relay
nach kind:30023 mit #d-filter ab — true wenn irgendeiner matcht.
timer-leaks vermieden per clearTimeout in publishOne + im mock-test.
3 tests grün.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
buildKind30023(args) baut unsigniertes kind:30023-event aus frontmatter
+ rewritten-body + cover-url. erzeugt pflicht-tags (d, title,
published_at) und bedingt optionale (summary aus description, image
aus coverUrl, t-tags aus tags[], client aus clientTag). plus
additionalTags-parameter für spätere task 15: license-tag und
imeta-tags (mit blossom-sha256) werden dort nach dem upload angehängt.
4 tests grün.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
rewriteImageUrls(md, mapping) ersetzt alle - und
[](link)-konstrukte per dateinamen-lookup durch
blossom-urls aus dem mapping. absolute urls bleiben unverändert,
unbekannte dateinamen bleiben stehen (kein strip). =WxH-größenhinweise
werden entfernt. URL-dekodierung für leerzeichen-dateinamen (z.b.
'03-config generieren.png' im moodle-post). resolveCoverUrl()-helper
für den cover-lookup. 7 tests grün.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
parseFrontmatter(md) trennt yaml-frontmatter vom markdown-body via
regex, parst yaml mit @std/yaml. Frontmatter-interface enthält
nostr-publish-konvention: optionale images-liste mit file/role/alt/
license/authors plus Author-interface (name/url/orcid). 4 tests grün.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>