diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09a7e5d..7d466a1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,6 +29,11 @@ jobs: - name: Configure Git User run: | + # Redirect global git config to a file in /tmp to avoid polluting the host system + # and ensure we have write permissions regardless of the workspace setup. + touch /tmp/.gitconfig_ci + echo "GIT_CONFIG_GLOBAL=/tmp/.gitconfig_ci" >> $GITHUB_ENV + export GIT_CONFIG_GLOBAL="/tmp/.gitconfig_ci" git config --global user.email "ci@git-cms.local" git config --global user.name "CI Bot" git config --global init.defaultBranch main @@ -53,19 +58,20 @@ jobs: container: image: mcr.microsoft.com/playwright:v1.57.0-jammy steps: - - uses: actions/checkout@v4 - - - uses: actions/setup-node@v4 - with: - node-version: '22' - cache: 'npm' - - # Git is required in the container to perform plumbing operations during E2E tests + # Git is required in the container to perform plumbing operations during E2E tests. + # Installing it BEFORE checkout ensures actions/checkout performs a real clone. - name: Install Git run: apt-get update && apt-get install -y --no-install-recommends git && rm -rf /var/lib/apt/lists/* + + - uses: actions/checkout@v4 - name: Configure Git User run: | + # Redirect global git config to a file in /tmp to avoid polluting the host system + # and ensure we have write permissions regardless of the workspace setup. + touch /tmp/.gitconfig_ci + echo "GIT_CONFIG_GLOBAL=/tmp/.gitconfig_ci" >> $GITHUB_ENV + export GIT_CONFIG_GLOBAL="/tmp/.gitconfig_ci" git config --global user.email "ci@git-cms.local" git config --global user.name "CI Bot" git config --global init.defaultBranch main diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aa2969..69ec5bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,48 @@ All notable changes to git-cms are documented in this file. +## [1.1.5] — 2026-02-14 + +### Fixed + +- **(Security) Git identity leakage:** Removed `git config --global` from host-level modification in CI workflow (`.github/workflows/ci.yml`). Scripts now use an isolated global config file via `GIT_CONFIG_GLOBAL` redirected to `/tmp`, preventing accidental modification of host global settings if workflows are executed locally (e.g., via `act`). +- `QUICK_REFERENCE.md`: `revert` command description corrected — sets state to `reverted`, not `draft` +- `QUICK_REFERENCE.md`: state machine diagram refined to accurately show `draft`→`reverted` transition +- `QUICK_REFERENCE.md`: HTTP API table uses canonical `optional` notation and clarifies optimistic concurrency for `publish` +- `docs/GETTING_STARTED.md`: migration walkthrough refined (separated idempotency/dry-run, clarified no-op behavior) +- `check-doc-drift.sh`: recursive search for deleted files and root-level documentation links +- `check-doc-drift.sh`: improved regex for CLI and API matching to prevent substring false positives and support underscores/digits +- `QUICK_REFERENCE.md`: state derivation rule clarified — "draft ref only" requires no `Status` trailer or `Status: draft` +- `QUICK_REFERENCE.md`: `<40-hex>` replaced with `` for hash-format-agnostic docs +- `docs/GETTING_STARTED.md`: migration walkthrough clarifies no-dry-run vs idempotency +- `check-doc-drift.sh`: API endpoint matching uses backtick-delimited grep (prevents substring false positives) +- `check-doc-drift.sh`: deleted-file search recurses into docs subdirectories +- `check-doc-drift.sh`: root GS links regex handles `../` relative path prefixes + +## [1.1.4] — 2026-02-14 + +### Changed + +- Consolidate and update documentation for M1.1, M1.2, M1.3, CE2, and CE3 +- `QUICK_REFERENCE.md` is now the canonical reference for all 9 CLI commands, 10 HTTP API endpoints, and state machine +- `docs/GETTING_STARTED.md` updated with version history, migration, unpublish/revert workflows +- `README.md` updated with missing CLI commands and choose-your-path navigation +- `ROADMAP.md` milestone statuses updated (M1.1, M1.2, M1.3, CE2, CE3 → complete) +- Root `GETTING_STARTED.md` and `REPO_WALKTHROUGH.md` replaced with redirect stubs +- `docs/ADR.md` file tree updated (removed `REPO_WALKTHROUGH.md`, added `CONTENT_ID_POLICY.md` and `LAYOUT_SPEC.md`) + +### Added + +- `scripts/check-doc-drift.sh` — automated doc drift detection (CLI commands, HTTP endpoints, stale references) +- `npm run check:docs` script + +### Fixed + +- Doc freshness banners now reference v1.1.4 (was v1.1.3) +- `check-doc-drift.sh`: CLI/API regex broadened to `[a-z0-9_-]+` for future-proofing +- `check-doc-drift.sh`: CLI command matching uses backtick-delimited grep to prevent substring false positives +- `check-doc-drift.sh`: `root_gs_links` check was computed but never evaluated (dead code) + ## [1.1.3] — 2026-02-14 ### Fixed @@ -102,6 +144,7 @@ All notable changes to git-cms are documented in this file. - **(P2) walkLimit divergence:** Extracted `HISTORY_WALK_LIMIT` as a shared exported constant used by both `_validateAncestry` and the server's history limit clamp [Unreleased]: https://github.com/flyingrobots/git-cms/compare/main...git-stunts +[1.1.4]: https://github.com/flyingrobots/git-cms/compare/v1.1.3...v1.1.4 [1.1.3]: https://github.com/flyingrobots/git-cms/compare/v1.1.2...v1.1.3 [1.1.2]: https://github.com/flyingrobots/git-cms/compare/v1.1.1...v1.1.2 [1.1.1]: https://github.com/flyingrobots/git-cms/compare/v1.1.0...v1.1.1 diff --git a/GETTING_STARTED.md b/GETTING_STARTED.md index b2ac507..3d0d147 100644 --- a/GETTING_STARTED.md +++ b/GETTING_STARTED.md @@ -1,73 +1,3 @@ -# Getting Started with Git CMS +# Getting Started -This quick guide is the lightweight entry point. For the full Docker-focused walkthrough in the docs folder, see [`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md). - ---- - -## Prerequisites - -- Git -- Node.js 22+ -- Docker Desktop (recommended for safe testing) - ---- - -## Installation - -### Option A: Local CLI Install - -```bash -git clone https://github.com/flyingrobots/git-cms.git -cd git-cms -npm install -npm link -``` - -### Option B: Docker (Recommended) - -```bash -git clone https://github.com/flyingrobots/git-cms.git -cd git-cms -npm run setup -npm run dev -``` - -Open the UI at [http://localhost:4638/](http://localhost:4638/). - ---- - -## First Article - -1. Click `+ New Article`. -2. Set a slug like `my-first-post`. -3. Add title + body content. -4. Click `Save Draft`. -5. Click `Publish` when ready. - ---- - -## CLI Basics - -```bash -# Draft reads content from stdin -echo "# Hello" | git cms draft hello-world "Hello World" - -# List drafts -git cms list - -# Publish -git cms publish hello-world - -# Show article -git cms show hello-world -``` - ---- - -## Safety Notes - -- Prefer Docker workflows while learning. -- Use a dedicated test repository for local CLI experimentation. -- Avoid running low-level Git plumbing in repositories you care about. - -See [`TESTING_GUIDE.md`](TESTING_GUIDE.md) for safety and cleanup procedures. +This guide has moved to [`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md). diff --git a/QUICK_REFERENCE.md b/QUICK_REFERENCE.md index e29f8d9..4d2b717 100644 --- a/QUICK_REFERENCE.md +++ b/QUICK_REFERENCE.md @@ -1,6 +1,8 @@ # Git CMS Quick Reference -One-page cheat sheet for Git CMS commands and concepts. +> Validated against v1.1.5 on 2026-02-14. + +One-page cheat sheet for Git CMS commands, API endpoints, and concepts. --- @@ -27,10 +29,25 @@ npm run demo # Watch a guided walkthrough | `npm test` | Run integration tests in Docker | | `npm run test:setup` | Run setup-script tests (BATS) | | `npm run test:local` | Run Vitest directly on host (advanced) | +| `npm run check:docs` | Check documentation drift against source code | --- -## CLI Commands (Inside Container) +## CLI Commands + +All 9 commands available via `node bin/git-cms.js ` or `git cms ` (if linked): + +| Command | Usage | Description | +|---------|-------|-------------| +| `draft` | `echo "body" \| git cms draft "Title"` | Create or update a draft (reads body from stdin) | +| `publish` | `git cms publish ` | Fast-forward published ref to match draft | +| `unpublish` | `git cms unpublish ` | Remove from published, keep as unpublished draft | +| `revert` | `git cms revert ` | Move article to 'reverted' state (creates new draft commit with `Status: reverted` trailer) | +| `list` | `git cms list` | List all draft articles | +| `show` | `git cms show ` | Print article title and body | +| `serve` | `git cms serve` | Start HTTP API + Admin UI on port 4638 | +| `migrate` | `git cms migrate` | Run pending layout migrations (forward-only, idempotent) | +| `layout-version` | `git cms layout-version` | Print repo and codebase layout versions | ```bash # Enter container @@ -45,12 +62,24 @@ node bin/git-cms.js list # Publish a draft node bin/git-cms.js publish my-slug +# Unpublish +node bin/git-cms.js unpublish my-slug + +# Revert to 'reverted' state +node bin/git-cms.js revert my-slug + # Read article content node bin/git-cms.js show my-slug # Start HTTP API + Admin UI node bin/git-cms.js serve +# Check layout version +node bin/git-cms.js layout-version + +# Run pending migrations +node bin/git-cms.js migrate + # Exit container exit ``` @@ -59,6 +88,63 @@ exit --- +## HTTP API + +All endpoints are served by `git cms serve` (default port 4638). Slugs are NFKC-normalized on the server. + +| Method | Path | Params / Body | Description | +|--------|------|---------------|-------------| +| `GET` | `/api/cms/list` | `?kind=articles\|published` | List articles by kind | +| `GET` | `/api/cms/show` | `?slug=xxx&kind=articles` | Read article content | +| `POST` | `/api/cms/snapshot` | `{ slug, title, body, trailers (optional) }` | Create or update a draft | +| `POST` | `/api/cms/publish` | `{ slug, sha (optional) }` | Publish a draft (omitting `sha` uses optimistic concurrency) | +| `POST` | `/api/cms/unpublish` | `{ slug }` | Unpublish an article | +| `POST` | `/api/cms/revert` | `{ slug }` | Revert to draft state | +| `GET` | `/api/cms/history` | `?slug=xxx&limit=50` | List version history (max 200) | +| `GET` | `/api/cms/show-version` | `?slug=xxx&sha=` | Read a specific historical version | +| `POST` | `/api/cms/restore` | `{ slug, sha }` | Restore a historical version as new draft | +| `POST` | `/api/cms/upload` | `{ slug, filename, data }` | Upload base64-encoded asset (encrypted) | + +--- + +## State Machine + +Articles move through four states. Transitions are enforced by `ContentStatePolicy.js`. + +```text +States: draft, published, unpublished, reverted + + revert + ┌──────────┐ ─────────────────► ┌──────────┐ + ┌───►│ draft │ │ reverted │ + │ └────┬──────┘ ◄──────────────── └──────────┘ + │ │ publish save + │ ▼ + │ ┌──────────┐ unpublish ┌─────────────┐ + │ │ published │───────────────►│ unpublished │ + │ └────┬──────┘ └──┬────────┬──┘ + │ │ publish (update) │ │ + │ └──────────┐ publish │ │ + │ ▼ │ │ │ + │ ┌──────────┐│ │ │ + │ │ published │◄──────┘ │ + │ └──────────┘ │ + │ save │ + └────────────────────────────────────────────┘ +``` + +**Effective state** is derived from which refs exist: +- Draft ref only (no `Status` trailer, or `Status: draft`) → `draft` +- Both draft + published refs → `published` +- Draft ref with `Status: unpublished` trailer → `unpublished` +- Draft ref with `Status: reverted` trailer → `reverted` + +**Restoring a version creates a new commit — git-cms never rewrites history.** + +See [`docs/LAYOUT_SPEC.md`](docs/LAYOUT_SPEC.md) for the full ref namespace and state derivation rules. + +--- + ## Core Concept ### Traditional CMS @@ -89,6 +175,22 @@ The trick: content lives in commit messages while commits point at the repo's em --- +## Architecture (Lego Blocks) + +```text +git-cms + -> CmsService (orchestrator) + -> @git-stunts/plumbing (Git execution) + -> @git-stunts/trailer-codec (trailer encode/decode) + -> @git-stunts/git-warp (commit graph primitives) + -> @git-stunts/git-cas (asset chunk + manifest storage) + -> @git-stunts/vault (secret/key resolution) +``` + +This replaced older in-repo helpers (`src/lib/git.js`, `src/lib/parse.js`, `src/lib/chunks.js`, `src/lib/secrets.js`). + +--- + ## Inspecting with Git ```bash @@ -107,20 +209,6 @@ git hash-object -t tree /dev/null --- -## Architecture (Lego Blocks) - -```text -git-cms - -> CmsService (orchestrator) - -> @git-stunts/plumbing (Git execution) - -> @git-stunts/trailer-codec (trailer encode/decode) - -> @git-stunts/git-warp (commit graph primitives) - -> @git-stunts/git-cas (asset chunk + manifest storage) - -> @git-stunts/vault (secret/key resolution) -``` - ---- - ## Commit Message Shape ```text @@ -139,6 +227,19 @@ Trailers are parsed by `@git-stunts/trailer-codec`. --- +## Testing Surface + +| Test File | Coverage | +|-----------|----------| +| `test/git.test.js` | Integration tests — CRUD, publish, unpublish, revert, state machine, content identity | +| `test/chunks.test.js` | Asset encryption and chunking | +| `test/server.test.js` | HTTP API endpoints, validation, error responses | +| `test/git-e2e.test.js` | Real-git smoke tests (subprocess forks) | +| `test/setup.bats` | Setup script tests (BATS) | +| `test/run-docker.sh` | Docker test harness | + +--- + ## Troubleshooting ### Cannot find module `@git-stunts/...` @@ -166,9 +267,21 @@ Start Docker Desktop (macOS/Windows) or start Docker service on Linux. ## Docs Map -- `README.md`: overview + quick start -- `TESTING_GUIDE.md`: safe test workflow -- `docs/GETTING_STARTED.md`: full walkthrough -- `docs/ADR.md`: architecture decision record -- `scripts/README.md`: helper script docs -- `test/README.md`: test suite docs +| File | Contents | +|------|----------| +| [`README.md`](README.md) | Overview + quick start | +| [`TESTING_GUIDE.md`](TESTING_GUIDE.md) | Safe test workflow | +| [`QUICK_REFERENCE.md`](QUICK_REFERENCE.md) | This file — canonical CLI/API/state machine reference | +| [`ROADMAP.md`](ROADMAP.md) | M0–M6 milestone plan | +| [`docs/GETTING_STARTED.md`](docs/GETTING_STARTED.md) | Full onboarding walkthrough | +| [`docs/ADR.md`](docs/ADR.md) | Architecture decision record | +| [`docs/CONTENT_ID_POLICY.md`](docs/CONTENT_ID_POLICY.md) | Slug validation and content identity rules | +| [`docs/LAYOUT_SPEC.md`](docs/LAYOUT_SPEC.md) | Ref namespace, layout versions, migration policy | +| [`scripts/README.md`](scripts/README.md) | Helper script docs | +| [`test/README.md`](test/README.md) | Test suite docs | + +**Removed docs** (redirect stubs remain for one release cycle): +- `GETTING_STARTED.md` (root) — moved to `docs/GETTING_STARTED.md` +- `REPO_WALKTHROUGH.md` — consolidated into this file + +**Repository:** [https://github.com/flyingrobots/git-cms](https://github.com/flyingrobots/git-cms) diff --git a/README.md b/README.md index 509da87..4885363 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ npm run dev The tests create, destroy, and manipulate Git repositories. Running low-level plumbing commands on your host filesystem is risky - a typo could affect your local Git setup. That's why we built Docker isolation into everything. -**Read more:** [TESTING_GUIDE.md](./TESTING_GUIDE.md) | [docs/GETTING_STARTED.md](./docs/GETTING_STARTED.md) | [docs/CONTENT_ID_POLICY.md](./docs/CONTENT_ID_POLICY.md) +**Read more:** [TESTING_GUIDE.md](./TESTING_GUIDE.md) | [docs/GETTING_STARTED.md](./docs/GETTING_STARTED.md) | [QUICK_REFERENCE.md](./QUICK_REFERENCE.md) | [docs/CONTENT_ID_POLICY.md](./docs/CONTENT_ID_POLICY.md) ## Features @@ -129,6 +129,32 @@ Publishing fast-forwards `refs/_blog/published/` to match the draft. git cms publish hello-world ``` +### 6. Unpublish / Revert + +```bash +# Remove from published, keep as unpublished draft +git cms unpublish hello-world + +# Revert to draft state +git cms revert hello-world +``` + +### 7. Layout Version / Migrate + +```bash +# Check repo vs codebase layout version +git cms layout-version + +# Run pending layout migrations (forward-only, idempotent) +git cms migrate +``` + +## Where to Go Next + +- **New user?** Start with [`docs/GETTING_STARTED.md`](./docs/GETTING_STARTED.md) +- **Operator / API user?** See [`QUICK_REFERENCE.md`](./QUICK_REFERENCE.md) for all CLI commands and HTTP endpoints +- **Architecture / rationale?** Read [`docs/ADR.md`](./docs/ADR.md) and [`docs/LAYOUT_SPEC.md`](./docs/LAYOUT_SPEC.md) + ## License Copyright © 2026 James Ross. This software is licensed under the [Apache License](./LICENSE), Version 2.0 diff --git a/REPO_WALKTHROUGH.md b/REPO_WALKTHROUGH.md index 39317b8..0506730 100644 --- a/REPO_WALKTHROUGH.md +++ b/REPO_WALKTHROUGH.md @@ -1,83 +1,3 @@ -# Git CMS Repository Walkthrough +# Repository Walkthrough -Technical orientation for the current codebase architecture. - ---- - -## 1. Core Model - -Git CMS stores article state in Git commits and refs, not SQL tables. - -- Entry point for orchestration: `src/lib/CmsService.js` -- Draft ref pattern: `{refPrefix}/articles/{slug}` -- Published ref pattern: `{refPrefix}/published/{slug}` -- Default `refPrefix`: `refs/_blog/dev` (overridable via `CMS_REF_PREFIX`) - -The service writes commit messages as content payloads and moves refs atomically with compare-and-swap semantics. - ---- - -## 2. Composition Layer - -`CmsService` composes the `@git-stunts/*` packages: - -- `@git-stunts/plumbing`: Git command execution and repository helpers -- `@git-stunts/trailer-codec`: trailer parsing/encoding -- `@git-stunts/git-warp`: commit graph primitives (`commitNode`, `showNode`) -- `@git-stunts/git-cas`: chunked asset storage and retrieval -- `@git-stunts/vault`: secret resolution for encryption keys - -This replaced older in-repo helpers such as `src/lib/git.js`, `src/lib/parse.js`, `src/lib/chunks.js`, and `src/lib/secrets.js`. - ---- - -## 3. Interfaces - -### CLI - -- File: `bin/git-cms.js` -- Commands: `draft`, `publish`, `list`, `show`, `serve` -- `draft` expects body from `stdin` - -### HTTP API - -- File: `src/server/index.js` -- Endpoints: `/api/cms/snapshot`, `/api/cms/publish`, `/api/cms/list`, `/api/cms/show`, `/api/cms/upload` -- Static UI served from `public/` - -### Admin UI - -- Files: `public/index.html`, `public/app.js` -- Uses the HTTP API for article lifecycle operations - ---- - -## 4. Testing Surface - -- Integration tests: `test/git.test.js`, `test/chunks.test.js`, `test/server.test.js` -- Setup script tests: `test/setup.bats` -- Docker test harness: `test/run-docker.sh`, `test/Dockerfile.bats` - -Key regressions covered include: - -- Ref-prefix correctness for chunk refs -- Error propagation from Git plumbing -- Symlink traversal hardening in static serving - ---- - -## 5. Operational Docs - -- Main overview: `README.md` -- Safety/testing operations: `TESTING_GUIDE.md` -- Deep architecture decisions: `docs/ADR.md` -- Detailed onboarding: `docs/GETTING_STARTED.md` -- Planning/status docs: `ROADMAP.md`, `docs/operations/` - ---- - -## 6. Repository Coordinates - -Canonical repository URL for this codebase: - -- +This content has been consolidated into [`QUICK_REFERENCE.md`](QUICK_REFERENCE.md). diff --git a/ROADMAP.md b/ROADMAP.md index 2d512f8..35ab410 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -147,7 +147,7 @@ INF3 (UI Redesign) ideally after M1.1 ## M1 — Core Content Model + State Machine -### M1.1 — Define canonical Content IDs + paths *(ADD — MUST EXIST)* +### M1.1 — Define canonical Content IDs + paths *(complete)* - **User Story:** As a developer, I understand how content is identified and where it lives. - **Requirements:** Slug rules (charset, length, uniqueness); rename semantics; ref naming conventions; content ID immutability policy. @@ -166,7 +166,7 @@ INF3 (UI Redesign) ideally after M1.1 --- -### M1.2 — Draft/Published state machine + transitions *(ADD — MUST EXIST)* +### M1.2 — Draft/Published state machine + transitions *(complete)* - **User Story:** As a developer, the Draft→Published→Unpublished→Reverted states are explicit and deterministic. - **Requirements:** State enum; allowed transitions; revert semantics (new commit vs ref move); unpublish semantics (tombstone vs delete). @@ -185,7 +185,7 @@ INF3 (UI Redesign) ideally after M1.1 --- -### M1.3 — Migration + repo layout spec *(ADD — MUST EXIST)* +### M1.3 — Migration + repo layout spec *(complete)* - **User Story:** As a maintainer, I know where content lives and how to migrate between versions. - **Requirements:** Ref namespace spec; index structure; migration strategy; backward compatibility policy. @@ -304,7 +304,7 @@ INF3 (UI Redesign) ideally after M1.1 --- -### CE2 — Add autosave for drafts +### CE2 — Add autosave for drafts *(complete)* - **User Story:** As an author, my draft is never lost because I sneezed near a browser tab. - **Requirements:** Debounce; conflict strategy; "last saved" indicator; local vs repo save decision. @@ -324,7 +324,7 @@ INF3 (UI Redesign) ideally after M1.1 --- -### CE3 — Add version history browser +### CE3 — Add version history browser *(complete)* - **User Story:** As an editor, I can browse and diff prior versions and restore one. - **Requirements:** Map commits to content IDs; diff view; restore action creates new commit; permissions later. @@ -591,6 +591,68 @@ INF3 (UI Redesign) ideally after M1.1 --- +## Backlog — Doc Tooling (Immediate Follow-up) + +### DOC1 — Add drift check to CI pipeline + +- **User Story:** As a maintainer, doc drift is caught automatically before merge — not manually after the fact. +- **Requirements:** Wire `npm run check:docs` into pre-push hook or CI workflow; fail the build on drift. +- **Scope:** CI config + optional git hook setup. +- **Est. Complexity:** ~30–80 LoC +- **Blocked By:** None + +--- + +### DOC2 — Add `--fix` mode to check-doc-drift.sh + +- **User Story:** As a maintainer, I can auto-regenerate the CLI and HTTP API tables in QUICK_REFERENCE.md from source instead of editing them by hand. +- **Requirements:** Parse `bin/git-cms.js` switch/case and `src/server/index.js` endpoint definitions; generate markdown tables; write them into QUICK_REFERENCE.md between sentinel comments. +- **Scope:** Script enhancement. +- **Est. Complexity:** ~100–300 LoC +- **Blocked By:** None + +--- + +### DOC3 — Markdown link checker + +- **User Story:** As a maintainer, broken internal links (`[text](path)` pointing to nonexistent files) are caught before merge. +- **Requirements:** Walk all `.md` files; resolve relative links; report broken targets; exclude external URLs. +- **Scope:** New script or integration with existing `check-doc-drift.sh`. +- **Est. Complexity:** ~80–200 LoC +- **Blocked By:** None + +--- + +### DOC4 — Generate state diagram from ContentStatePolicy.js + +- **User Story:** As a maintainer, the state machine diagram in QUICK_REFERENCE.md stays in sync with code automatically — no manual ASCII art updates after policy changes. +- **Requirements:** Read `TRANSITIONS` map from `ContentStatePolicy.js`; generate ASCII or Mermaid diagram; write into QUICK_REFERENCE.md between sentinel comments. +- **Scope:** Script + sentinel markers in QUICK_REFERENCE.md. +- **Est. Complexity:** ~100–250 LoC +- **Blocked By:** None + +--- + +### DOC5 — Add `test:docs` target for drift script regression tests + +- **User Story:** As a maintainer, I can verify that `check-doc-drift.sh` itself catches real drift — with intentional drift scenarios as test fixtures. +- **Requirements:** Fixture files with missing CLI commands, missing endpoints, stale references; test harness runs drift script against fixtures and asserts expected failures. +- **Scope:** Test fixtures + test runner script + `npm run test:docs` target. +- **Est. Complexity:** ~80–200 LoC +- **Blocked By:** None + +--- + +### DOC6 — Port check-doc-drift.sh to JavaScript + +- **User Story:** As a maintainer, the doc drift checker uses the same language and test patterns as the rest of the codebase — improving maintainability and cross-platform support. +- **Requirements:** Rewrite `check-doc-drift.sh` as a Node.js script; same checks (CLI commands, HTTP endpoints, stale references, root GS links); integrate with Vitest for testability. +- **Scope:** New JS script replacing shell script; update `npm run check:docs`. +- **Est. Complexity:** ~150–400 LoC +- **Blocked By:** None (DOC5 provides regression safety net) + +--- + ## Already Published - `@git-stunts/plumbing` diff --git a/TESTING_GUIDE.md b/TESTING_GUIDE.md index 0459465..862c356 100644 --- a/TESTING_GUIDE.md +++ b/TESTING_GUIDE.md @@ -195,6 +195,8 @@ docker compose run --rm test - Runs Vitest integration tests - Creates temporary Git repos in `/tmp` - Tests CRUD operations, encryption, API endpoints +- Version history browsing and restore (CE3) +- Layout migration framework (M1.3) - Cleans up after completion **Safe because:** All tests run in an isolated Docker container with temporary repos. diff --git a/docs/ADR.md b/docs/ADR.md index 43478b1..0a653ad 100644 --- a/docs/ADR.md +++ b/docs/ADR.md @@ -1741,7 +1741,8 @@ git-cms/ │ └── app.js ├── docs/ │ ├── GETTING_STARTED.md -│ ├── REPO_WALKTHROUGH.md +│ ├── CONTENT_ID_POLICY.md +│ ├── LAYOUT_SPEC.md │ └── ADR.md # This document ├── Dockerfile # Multi-stage build ├── docker-compose.yml # Dev/test orchestration diff --git a/docs/GETTING_STARTED.md b/docs/GETTING_STARTED.md index d118c07..d600888 100644 --- a/docs/GETTING_STARTED.md +++ b/docs/GETTING_STARTED.md @@ -1,5 +1,7 @@ # Getting Started with Git CMS +> Validated against v1.1.5 on 2026-02-14. + **⚠️ IMPORTANT: This project manipulates Git repositories at a low level. Always use Docker for testing to protect your local Git setup.** --- @@ -111,6 +113,12 @@ node bin/git-cms.js publish hello-world # Read it back node bin/git-cms.js show hello-world +# Unpublish (removes from published, keeps as unpublished draft) +node bin/git-cms.js unpublish hello-world + +# Revert to draft state +node bin/git-cms.js revert hello-world + # Exit the container exit ``` @@ -141,6 +149,47 @@ exit - Commits pointing to the "empty tree" (no files touched!) - Refs acting as pointers to "current" versions +### Step 6: Browse Version History + +Every draft save creates a new commit, so you get infinite version history for free. + +In the Admin UI: +1. Open an article you've edited multiple times +2. Expand the **History** panel on the right +3. Click any version to preview its content +4. Click **Restore** to bring back that version as a new draft + +The HTTP API provides three endpoints for version history — see [`QUICK_REFERENCE.md`](../QUICK_REFERENCE.md) for the full API table: +- `GET /api/cms/history` — list version summaries +- `GET /api/cms/show-version` — read a specific historical version +- `POST /api/cms/restore` — restore a version as a new draft + +**Restoring creates a new commit — no history is rewritten.** + +### Step 7: Run Migrations (When Upgrading) + +When upgrading git-cms to a new version, check whether your repository's layout needs migration: + +```bash +# Inside the container +docker compose exec app sh + +# Check current layout version +node bin/git-cms.js layout-version +# Repo: v0 +# Codebase: v1 + +# Run pending migrations +node bin/git-cms.js migrate +# Migrated layout: v0 → v1 (applied: 1) +``` + +Things to know about migrations: +- **Idempotent:** Safe to run multiple times — already-applied migrations are skipped. If no structural changes exist between repo and codebase, `node bin/git-cms.js migrate` will be a no-op. +- **Forward-only:** Rollback is not supported (by design). +- **No dry-run mode:** The tool does not provide a preview mode to simulate changes. Use `node bin/git-cms.js layout-version` to check whether a migration is pending. +- **Backup first:** Run `git clone --mirror ` before migrating production repos. + --- ## Understanding the Safety Model diff --git a/package.json b/package.json index b774410..217bdd6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "git-cms", - "version": "1.1.3", + "version": "1.1.5", "description": "A serverless, database-free CMS built on Git plumbing.", "type": "module", "bin": { @@ -13,6 +13,7 @@ "quickstart": "./scripts/quickstart.sh", "setup": "./scripts/setup.sh", "check:deps": "node scripts/check-dependency-integrity.mjs", + "check:docs": "./scripts/check-doc-drift.sh", "test": "./test/run-docker.sh", "test:setup": "./test/run-setup-tests.sh", "test:local": "vitest run", diff --git a/scripts/check-doc-drift.sh b/scripts/check-doc-drift.sh new file mode 100755 index 0000000..2e29e8f --- /dev/null +++ b/scripts/check-doc-drift.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# +# check-doc-drift.sh — verify QUICK_REFERENCE.md stays in sync with source code +# +# Checks: +# 1. All CLI commands from bin/git-cms.js appear in QUICK_REFERENCE.md +# 2. All HTTP endpoints from src/server/index.js appear in QUICK_REFERENCE.md +# 3. Deleted file names are not referenced as active links +# 4. docs/ files don't link to root GETTING_STARTED.md (canonical is docs/) +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ROOT="$(dirname "$SCRIPT_DIR")" +QUICK_REF="$ROOT/QUICK_REFERENCE.md" +CLI_FILE="$ROOT/bin/git-cms.js" +SERVER_FILE="$ROOT/src/server/index.js" + +errors=0 + +# ── 1. CLI commands ────────────────────────────────────────────────────────── +# Extract command names from the switch/case block in bin/git-cms.js +cli_commands=$(grep -oE "case '[a-z0-9_-]+'" "$CLI_FILE" | sed "s/case '//;s/'//") + +for cmd in $cli_commands; do + if ! grep -q "\`$cmd\`" "$QUICK_REF"; then + echo "DRIFT: CLI command '$cmd' missing from QUICK_REFERENCE.md" + errors=$((errors + 1)) + fi +done + +# ── 2. HTTP endpoints ─────────────────────────────────────────────────────── +# Extract API paths from src/server/index.js +api_paths=$(grep -oE "pathname === '/api/cms/[a-z0-9_/-]+'" "$SERVER_FILE" | sed "s/pathname === '//;s/'//g" | sort -u) + +for ep in $api_paths; do + if ! grep -q "\`$ep\`" "$QUICK_REF"; then + echo "DRIFT: HTTP endpoint '$ep' missing from QUICK_REFERENCE.md" + errors=$((errors + 1)) + fi +done + +# ── 3. Deleted-file references ─────────────────────────────────────────────── +# These files have been replaced with redirect stubs. No other doc should link to them. +deleted_files=("REPO_WALKTHROUGH.md") + +for file in "${deleted_files[@]}"; do + # Search all markdown files recursively except the stub itself, QUICK_REFERENCE (documents the removal), and CHANGELOG (historical entries) + offenders=$(grep -rl --include='*.md' "$file" "$ROOT" 2>/dev/null | grep -v "$ROOT/$file" | grep -v "$ROOT/QUICK_REFERENCE.md" | grep -v "$ROOT/CHANGELOG.md" || true) + if [ -n "$offenders" ]; then + echo "DRIFT: Deleted file '$file' still referenced in: $offenders" + errors=$((errors + 1)) + fi +done + +# ── 4. Root GETTING_STARTED.md links from docs/ ───────────────────────────── +# docs/ files should link to docs/GETTING_STARTED.md, not the root redirect stub. +# This search is recursive to catch subdirectories like docs/operations/ +root_gs_links=$(grep -rnE '\[.*\]\((\.\.\/)*GETTING_STARTED\.md\)' "$ROOT/docs" --include='*.md' 2>/dev/null || true) +if [ -n "$root_gs_links" ]; then + echo "DRIFT: docs/ files link to root GETTING_STARTED.md instead of docs/GETTING_STARTED.md:" + echo "$root_gs_links" + errors=$((errors + 1)) +fi + +if [ "$errors" -gt 0 ]; then + echo "" + echo "FAIL: $errors doc-drift issue(s) found." + exit 1 +fi + +echo "OK: All CLI commands and HTTP endpoints documented. No stale references."