@kailua-packages/headless-cli
v0.10.0
Published
CLI (hcms) fuer Schema-/Content-Pull+Push, Environments, Promote, Seed, Typegen und Doku-Pull gegen die Kailua Headless-CMS API.
Readme
@kailua-packages/headless-cli
hcms — schlanke CLI fuer Schema, Content, Environments, Promote, Seed, Types
und Doku gegen die Kailua Headless-CMS REST-API (apps/headless). Nutzt intern
@kailua-packages/headless-sdk und baut nur auf Node-Bordmitteln
(util.parseArgs, node:readline) auf — keine schweren Dependencies.
Verwendung
Im Monorepo via tsx (kein Build noetig):
npm run hcms --workspace=@kailua-packages/headless-cli -- <befehl> [optionen]
# oder direkt
npx tsx packages/headless-cli/src/cli.ts <befehl> [optionen]Nach npm run build steht das Bin hcms (dist/cli.js) zur Verfuegung.
Authentifizierung & Konfiguration
Alle API-Befehle brauchen eine Basis-URL und ein Token. Die URL kommt aus
--url, der Env-Variable HCMS_URL oder der hcms.config.json (siehe unten).
Das Token kommt ausschliesslich aus --token oder der Env-Variable
HCMS_TOKEN — es gehoert NIE in die Config-Datei und NIE ins Repo:
export HCMS_URL="https://acme.kailua-packages.ch/headless"
export HCMS_TOKEN="hcms_live_xxx"Benoetigte Token-Scopes je Befehl: read (types/docs/content pull),
write+publish (content push, seed), manage (schema pull/push/diff,
env list/clone/status, promote, seed).
hcms.config.json
Projektweite Konfiguration im Frontend-Repo (committen erlaubt — enthaelt keine Secrets). Alle Felder optional; Pfade relativ zum Repo-Root:
{
"url": "https://acme.kailua-packages.ch/headless",
"environment": "production",
"schemaDir": "cms/schema",
"contentDir": "cms/content",
"typesOut": "cms/types.generated.ts",
"docsOut": "cms/llms-full.txt"
}| Feld | Default | Bedeutung |
| ------------- | ------------------------ | ------------------------------------------------ |
| url | – | Basis-URL der Headless-App (inkl. /headless) |
| environment | Default-Env der Org | Standard-Environment fuer alle Befehle (--env uebersteuert) |
| schemaDir | cms/schema | Zielordner fuer schema pull/push/diff (auch theme unter <schemaDir>/themes/) |
| blocksDir | cms/blocks | Zielordner fuer blocks pull/push/diff |
| layoutsDir | cms/layouts | Zielordner fuer layouts pull/push/diff |
| contentDir | cms/content | Zielordner fuer content pull/push |
| typesOut | cms/types.generated.ts | Zieldatei fuer types generate |
| docsOut | cms/llms-full.txt | Zieldatei fuer docs pull |
Aufloesungs-Reihenfolge: CLI-Flag > Env-Variable (HCMS_URL) > hcms.config.json.
Token: nur --token > HCMS_TOKEN (bewusst keine Config-Option; ein
token-Feld in der Config wird mit Warnung ignoriert).
Die Datei wird vom Arbeitsverzeichnis aus aufwaerts gesucht (wie
package.json-Lookup); relative Ausgabepfade aus der Config sind relativ zum
Verzeichnis der Config-Datei.
Befehls-Uebersicht
| Befehl | Zweck |
| --- | --- |
| hcms init [dir] [--url] [--token] [--skip-install] | Frontend-Starter scaffolden (Template + .env.local + docs pull + types generate) |
| hcms docs pull [--out] | Per-Org-LLM-Doku (llms-full.txt) als Datei ins Repo laden |
| hcms types generate [--out] [--env] [--watch] [--interval 5] [--scaffold-blocks] [--dir components/blocks] | TypeScript-Typen aus Collection-Schema und Bloecken generieren (optional Block-Komponenten scaffolden) |
| hcms schema pull [--env] [--out <dir>] | Schema als Dateien ziehen (manifest.json + collections/*.json + components/*.json) |
| hcms schema push [--env] [--dry-run] | Schema-Dateien idempotent pushen (upsert per slug, loescht NIE) |
| hcms schema diff [--env] | Lokales Schema gegen ein Environment diffen (Server-Diff via Dry-Run) |
| hcms blocks pull [--env] [--out <dir>] | Block-Definitionen (DB, source!=code) als hcms/block@1-Dateien ziehen (<blocksDir>/<slug>.json) |
| hcms blocks push [--env] [--dry-run] | Block-Dateien idempotent pushen (upsert per slug, loescht NIE; Built-in-Slugs → 422) |
| hcms blocks diff [--env] | Lokale Bloecke gegen ein Environment diffen (Server-Diff via Dry-Run) |
| hcms layouts pull [--env] [--out <dir>] | Layouts als hcms/layout@1-Dateien ziehen (<layoutsDir>/<slug>.json) |
| hcms layouts push [--env] [--dry-run] | Layout-Dateien pushen (Baum-Validierung gegen Ziel-Env; unbekannte Block-Typen → 422) |
| hcms layouts diff [--env] | Lokale Layouts gegen ein Environment diffen |
| hcms theme pull [--env] [--out <dir>] | Theme-Tokens als hcms/theme@1-Dateien ziehen (<schemaDir>/themes/<slug>.json) |
| hcms theme push [--env] [--dry-run] | Theme-Dateien pushen (upsert per name) |
| hcms theme diff [--env] | Lokale Themes gegen ein Environment diffen |
| hcms content pull --collection X [--env] [--status] [--out] | Eintraege inkl. syncKey als hcms/content@1-Datei ziehen |
| hcms content push --collection X [--env] [--file] | Eintraege idempotent pushen (Bulk-Upsert per syncKey+locale, Chunks a 100) |
| hcms env list | Environments der Organisation auflisten |
| hcms env clone --from <key> --to <key> [--name] [--content] [--pages] [--theme] | Environment in ein NEUES Environment klonen |
| hcms env status --op <id> | Status einer Clone-/Promote-Operation abfragen |
| hcms promote --from A --to B [--collections a,b] [--with-content] [--pages] [--menus] [--themes] [--conflict overwrite\|skip\|fail] [--dry-run] | Schema (+ optional Content) in ein bestehendes Environment promoten |
| hcms seed --template blog [--env] [--file] | Beispiel-Schema + -Content einspielen (idempotent) |
Deprecated-Aliase (Warnung, bleiben uebergangsweise): schema export →
schema pull, schema import → schema push; content export/content
import behalten ihr altes Verhalten (JSON/CSV-Export bzw. reines Anlegen ohne
Upsert). Der fruehere migrate-Platzhalter ist zugunsten von schema push
und promote entfallen.
Setup & Onboarding
hcms init [dir]
Scaffoldet ein Next.js-Frontend aus dem Starter-Template
(templates/frontend-starter im Monorepo bzw. im npm-Paket eingebettet):
- Template in den Zielordner kopieren (muss leer/neu sein).
.env.localmitHCMS_URL/HCMS_TOKENschreiben (Token NIE in die Config).hcms.config.jsonim Ziel mit der URL aktualisieren.npm install(via--skip-installabwaehlbar).- Best-effort
docs pull+types generate(Fehler nur als Warnung).
Fehlende --url/--token werden interaktiv erfragt.
hcms init my-site --url https://acme.kailua-packages.ch/headless --token hcms_live_xxx
cd my-site && npm run devhcms seed
Spielt ein Seed-Template in fester Reihenfolge ein: Schema → Blocks →
Layouts → Content (Blocks vor Layouts, weil der Server den Layout-Baum gegen
die Block-Palette des Ziel-Envs validiert). Alle Schritte sind idempotent
(Upsert per Slug bzw. per stabilem syncKey — Wiederholung erzeugt keine
Duplikate). Relationen im Content referenzieren per "$ref:<syncKey>" und
werden von der CLI gegen die zurueckgegebenen Entry-ids aufgeloest.
Eingebaut (--template blog):
- Collections:
author,category,blog - Block-Definition:
testimonial(Kundenstimme —quotePflichtfeld,author_name,author_image) alshcms/block@1 - Layout:
landing-pagealshcms/layout@1— verschachtelter Baumsection > container > [hero, columns 1:1 [richtext | testimonial], cta]aus Built-in-Bloecken plus dem Seed-Blocktestimonial - Content: 2 Autoren, 3 Kategorien, 5 Blog-Posts
Die Abschnitte blocks/layouts sind optional; ein Template ohne sie seedet
nur Schema + Content (abwaertskompatibel). Nach dem Seed sofort einsatzbereit:
neue Seite aus dem Layout landing-page anlegen.
hcms seed --template blog --env dev
hcms seed --file mein-seed.json # eigenes hcms/seed@1-Templatehcms types generate
Laedt GET /api/v1/collections?include=fields (rohe Felddefinitionen,
read-Scope) UND GET /api/v1/blocks?environment= (aufgeloeste
Block-Definitionen: Built-ins + DB) und generiert eine TypeScript-Typendatei:
- pro Collection ein Data-Interface (Literal-Unions aus choices, discriminated
unions fuer
flexible_contentueberacf_fc_layout, Inline-Interfaces fuer group/repeater), eine Entry-Huelle (<Name>Entry extends HcmsEntryEnvelope<...>) und die MapHcmsCollectionMap(Slug → Data-Typ) fuercreateClient<HcmsCollectionMap>(); - pro Block ein
<Slug>Props-Interface (gleiches Feld-Mapping wie Collections; Container-Bloecke bekommenchildrenNICHT als Prop —renderBlocksrendert die Kinder aus dem Block-Baum), plusHcmsBlockRegistry(Slug →ComponentType<Props>) undHcmsBlockSlug(Union). Der einzige Import der generierten Datei ist dann ein reinerimport type { ComponentType } from "react".
Geschrieben wird nur bei geaendertem Schema-Hash (deckt Collections UND Bloecke
ab); --watch pollt (Default 5s, --interval <sek>).
hcms types generate
hcms types generate --env dev --watch --interval 10--scaffold-blocks [--dir components/blocks]
Erzeugt zusaetzlich fuer jeden Block-Slug OHNE vorhandene Datei einen
typisierten Komponenten-Stub <dir>/<slug>.tsx (Default-dir:
components/blocks). Die Props kommen aus dem <Slug>Props-Interface der
generierten Typendatei; Container-Bloecke rendern props.children.
Bestehende Dateien werden NIE ueberschrieben. Am Ende gibt die CLI ein
Registry-Snippet (import-Zeilen + typisierte blocks-Map) zum Kopieren nach
components/blocks/registry.tsx auf stdout aus.
hcms types generate --env dev --scaffold-blocks
hcms types generate --scaffold-blocks --dir src/blockshcms docs pull
Laedt die per-Organisation generierte LLM-Doku (GET /api/v1/llms-full.txt,
Bearer-Token) und schreibt sie als Datei ins Repo (Default cms/llms-full.txt
bzw. docsOut). Als lokale Datei kann z.B. Claude Code die Doku direkt lesen.
Schema-as-Code
hcms schema pull
GET /management/schema (manage-Scope) und schreibt in den schemaDir
(Default cms/schema):
cms/schema/
manifest.json # { "$format": "hcms/manifest@1", environment, pulledAt }
collections/<slug>.json # { "$format": "hcms/collection@1", slug, name, settings, fields }
components/<slug>.json # { "$format": "hcms/component@1", ... }Dateien sind kanonisch serialisiert (Objekt-Keys sortiert, 2-Space-Indent,
Newline am Ende) → stabile Git-Diffs. schemaVersion wird bewusst NICHT
geschrieben (Server-Zaehler). Nicht mehr existierende Dateien werden beim Pull
entfernt (Pull ist ein Spiegel des Server-Stands).
hcms schema push / hcms schema diff
Liest die Dateien aus dem schemaDir und PUT /management/schema:
idempotenter Upsert per slug, loescht nie (nur im Ziel vorhandene Objekte
werden lediglich gemeldet). --dry-run (bzw. schema diff) liefert den
Server-Diff, ohne zu schreiben. Environment-Prioritaet: --env > Config >
manifest.json (Herkunft des letzten Pull).
hcms schema pull --env dev
hcms schema push --env production --dry-run # erst pruefen ...
hcms schema push --env production # ... dann anwendenBaukasten-as-Code (Bloecke, Layouts, Theme)
Analog zu Schema-as-Code werden auch Bloecke, Layouts und Theme-Tokens als
kanonische, Git-diffbare Dateien gepflegt (alle Management-API, manage-Scope,
idempotenter Upsert per slug, loescht nie — nur-im-Ziel wird gemeldet).
--dry-run (bzw. ... diff) liefert den Server-Diff +neu ~aktualisiert
unveraendert, ohne zu schreiben.
hcms blocks pull|push|diff
GET/PUT /management/blocks schreibt/liest eine hcms/block@1-Datei je Slug
im blocksDir (Default cms/blocks). Der Pull exportiert nur
DB-Definitionen (source != code) — Built-in-Bloecke sind Code und werden
nie exportiert. Ein Push mit einem Built-in-Slug lehnt der Server mit 422
VALIDATION_ERROR (Slug-Liste) ab. schemaVersion (Server-Zaehler) landet nie
in der Datei.
hcms layouts pull|push|diff
GET/PUT /management/layouts mit hcms/layout@1-Dateien im layoutsDir
(Default cms/layouts). Der Server validiert den Block-Baum gegen die
aufgeloesten Block-Definitionen des Ziel-Environments — unbekannte
Block-Typen → 422. Beim Deployen daher erst blocks push, dann layouts
push (sonst schlaegt die Baum-Validierung fehl).
hcms theme pull|push|diff
GET/PUT /management/themes mit hcms/theme@1-Dateien unter
<schemaDir>/themes/<slug>.json (Schema + Themes eines Environments liegen so
beisammen). HcmsTheme hat keine slug-Spalte — der Server upsertet per
name. Der Datei-slug (= kebab(name)) dient nur als stabiler,
Git-diffbarer Dateiname und wird beim Push mitgeschickt; der Server matcht
ueber name.
# Empfohlene Deploy-Reihenfolge (dev -> prod):
hcms blocks push --env prod --dry-run && hcms blocks push --env prod
hcms layouts push --env prod --dry-run && hcms layouts push --env prod
hcms theme push --env prod --dry-run && hcms theme push --env prodContent
hcms content pull
Laedt alle Eintraege einer Collection (Delivery-GET, paginiert, inkl.
syncKey/locale) und schreibt eine hcms/content@1-Datei (kanonisch,
Default <contentDir>/<slug>.json):
{
"$format": "hcms/content@1",
"collection": "blog",
"entries": [
{ "syncKey": "…", "locale": "de", "status": "published", "slug": "…", "data": { } }
]
}hcms content push
Liest die Datei und upsertet per Bulk-API (POST
/collections/X/entries/bulk, action upsert, Matching per syncKey+locale,
Chunks a 100). Idempotent wiederholbar; am Ende Zusammenfassung
succeeded/failed (Fehler-Items einzeln, Exit-Code 1 bei Fehlern).
hcms content pull --collection blog --env dev
hcms content push --collection blog --env productionEnvironments & Promote
hcms env list / hcms env clone / hcms env status
hcms env list
hcms env clone --from production --to dev --name "Entwicklung" --content --pages --theme
hcms env status --op <operationId>clone legt ein NEUES Environment an (Schema immer; --content,
--pages [Seiten+Menus], --theme optional). Der Klon laeuft serverseitig
idempotent — nach einem Netzwerk-Timeout ist die Wiederholung gefahrlos
(bereits kopierte Objekte werden uebersprungen); Status via env status.
hcms promote
Promotet Schema (+ optional Content/Seiten/Menus/Themes) von einem Quell- in
ein BESTEHENDES Ziel-Environment (POST /management/environments/promote).
--conflict steuert die Strategie bei abweichenden Ziel-Objekten
(CLI-Default: overwrite; fail bricht VOR dem ersten Write ab).
hcms promote --from dev --to production --dry-run # Diff ansehen
hcms promote --from dev --to production --with-content --pages # anwenden
hcms promote --from dev --to production --collections blog,page --conflict skipDry-Run gibt den Schema-Diff und die Content-Zaehler je Collection aus; die echte Ausfuehrung Stats (neu/ueberschrieben/uebersprungen/unveraendert) und Warnings.
Deprecated: content export / content import
content export schreibt JSON oder CSV (Format aus --format oder der
--out-Endung; data-Felder bei CSV flach). content import legt Eintraege
per Einzel-POST an (nur create, kein Upsert). Beide geben eine
Deprecation-Warnung aus — fuer Roundtrips content pull/content push
verwenden.
Tests & Build
npm run test --workspace=@kailua-packages/headless-cli # vitest (Mock-fetch, keine echte HTTP/DB)
npm run typecheck --workspace=@kailua-packages/headless-cli
npm run build --workspace=@kailua-packages/headless-clitemplates/ (Seed-Templates) ist eingecheckt und via files im npm-Paket
enthalten. Fuer ein npm-Publish muss der Build zusaetzlich das Starter-Template
(<repo>/templates/frontend-starter) nach templates/frontend-starter
kopieren (siehe Kommentar in package.json) — im Monorepo greift hcms init
per Dev-Fallback direkt auf <repo>/templates/frontend-starter zu.
