mirror of
https://github.com/kbenestad/mdcms.git
synced 2026-06-18 15:24:32 +00:00
Merge pull request #4 from kbenestad/mdcms_cli
mdcms v0.3 — CLI rewrite
This commit is contained in:
commit
a3e328e1b8
21 changed files with 878 additions and 1123 deletions
121
.github/workflows/release.yml
vendored
Normal file
121
.github/workflows/release.yml
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
name: Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Build — ${{ matrix.label }}
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
label: Linux amd64
|
||||||
|
binary_name: mdcms
|
||||||
|
artifact_name: mdcms-linux-amd64
|
||||||
|
make_deb: true
|
||||||
|
|
||||||
|
- os: macos-latest
|
||||||
|
label: macOS arm64
|
||||||
|
binary_name: mdcms
|
||||||
|
artifact_name: mdcms-macos-arm64
|
||||||
|
make_deb: false
|
||||||
|
|
||||||
|
- os: windows-latest
|
||||||
|
label: Windows amd64
|
||||||
|
binary_name: mdcms.exe
|
||||||
|
artifact_name: mdcms-windows-amd64
|
||||||
|
make_deb: false
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install pyinstaller click pyyaml
|
||||||
|
|
||||||
|
- name: Build binary
|
||||||
|
run: pyinstaller --onefile --name mdcms mdcms.py
|
||||||
|
|
||||||
|
- name: Rename binary (non-Windows)
|
||||||
|
if: matrix.os != 'windows-latest'
|
||||||
|
run: mv dist/mdcms dist/${{ matrix.artifact_name }}
|
||||||
|
|
||||||
|
- name: Rename binary (Windows)
|
||||||
|
if: matrix.os == 'windows-latest'
|
||||||
|
run: mv dist/mdcms.exe dist/${{ matrix.artifact_name }}.exe
|
||||||
|
|
||||||
|
- name: Build .deb (Linux only)
|
||||||
|
if: matrix.make_deb
|
||||||
|
run: |
|
||||||
|
VERSION=${GITHUB_REF_NAME#v}
|
||||||
|
gem install fpm --no-document
|
||||||
|
fpm \
|
||||||
|
-s dir -t deb \
|
||||||
|
-n mdcms \
|
||||||
|
-v "$VERSION" \
|
||||||
|
--description "MD-CMS companion CLI — manage and build MD-CMS sites" \
|
||||||
|
--url "https://github.com/kbenestad/mdcms" \
|
||||||
|
--maintainer "Kristian Benestad" \
|
||||||
|
--license "Apache-2.0" \
|
||||||
|
--architecture amd64 \
|
||||||
|
--category utils \
|
||||||
|
dist/mdcms-linux-amd64=/usr/local/bin/mdcms
|
||||||
|
|
||||||
|
- name: Upload binary artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.artifact_name }}
|
||||||
|
path: |
|
||||||
|
dist/${{ matrix.artifact_name }}
|
||||||
|
dist/${{ matrix.artifact_name }}.exe
|
||||||
|
if-no-files-found: ignore
|
||||||
|
|
||||||
|
- name: Upload .deb artifact
|
||||||
|
if: matrix.make_deb
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: deb-package
|
||||||
|
path: "*.deb"
|
||||||
|
|
||||||
|
release:
|
||||||
|
name: Create GitHub Release
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Download all artifacts
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
path: artifacts
|
||||||
|
merge-multiple: false
|
||||||
|
|
||||||
|
- name: List artifacts
|
||||||
|
run: find artifacts -type f
|
||||||
|
|
||||||
|
- name: Create release
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
PRERELEASE=""
|
||||||
|
if [[ "${{ github.ref_name }}" == *"-"* ]]; then
|
||||||
|
PRERELEASE="--prerelease"
|
||||||
|
fi
|
||||||
|
gh release create "${{ github.ref_name }}" \
|
||||||
|
--repo "${{ github.repository }}" \
|
||||||
|
--title "mdcms ${{ github.ref_name }}" \
|
||||||
|
--generate-notes \
|
||||||
|
$PRERELEASE \
|
||||||
|
artifacts/mdcms-linux-amd64/mdcms-linux-amd64 \
|
||||||
|
artifacts/mdcms-macos-arm64/mdcms-macos-arm64 \
|
||||||
|
artifacts/mdcms-windows-amd64/mdcms-windows-amd64.exe \
|
||||||
|
artifacts/deb-package/*.deb
|
||||||
125
CLAUDE.md
125
CLAUDE.md
|
|
@ -6,55 +6,92 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||||
|
|
||||||
MD-CMS is a markdown-based static site system with two distinct parts:
|
MD-CMS is a markdown-based static site system with two distinct parts:
|
||||||
|
|
||||||
1. **`mdcms.py`** — a zero-dependency Python 3 CLI tool (standard library only). It scans content, generates `nav.yml` and `search.json`, validates config, and packages a zip for deployment.
|
1. **`mdcms.py`** — a Python 3 CLI tool (`click` + `PyYAML`). Manages a registry of sites, scans content, generates `nav.yml` and `search.json`, and is designed for both local use and GitHub Actions pipelines.
|
||||||
2. **`website/index.html`** — a single-file browser renderer that reads markdown, config, and nav at runtime entirely client-side. There is no build pipeline, no compilation, no server.
|
2. **`app/index.html`** — a single-file browser renderer that reads markdown, config, and nav at runtime entirely client-side. There is no build pipeline, no compilation, no server.
|
||||||
|
|
||||||
The `website/` folder is the deployable artifact. `mdcms.py` lives outside it.
|
The `app/` folder is the deployable artifact and the starter template downloaded when registering a new site. `mdcms.py` lives outside it.
|
||||||
|
|
||||||
## Running the CLI
|
## Repository layout
|
||||||
|
|
||||||
```bash
|
```
|
||||||
python3 mdcms.py
|
mdcms.py ← CLI tool
|
||||||
|
pyproject.toml ← packaging (entry point, dependencies)
|
||||||
|
app/
|
||||||
|
index.html ← renderer + v0.3 version marker
|
||||||
|
config.yml ← starter config + v0.3 version marker
|
||||||
|
nav.yml ← generated
|
||||||
|
search.json ← generated
|
||||||
|
pages/
|
||||||
|
posts/
|
||||||
|
assets/
|
||||||
|
docs/
|
||||||
|
banner/
|
||||||
|
documentation.md
|
||||||
|
knownlimitations.md
|
||||||
|
quickstart.md
|
||||||
|
.github/workflows/release.yml ← cross-platform release builds
|
||||||
|
samplesite/ ← reference implementation (not deployed)
|
||||||
```
|
```
|
||||||
|
|
||||||
This opens an interactive menu. Key options:
|
## CLI commands
|
||||||
|
|
||||||
| Option | Action |
|
Install: `pip install mdcms` / `pipx install mdcms` — or use the standalone binary from a GitHub release.
|
||||||
|--------|--------|
|
|
||||||
| 1 | Validate config + nav, rebuild search.json, export `website.zip` |
|
|
||||||
| 2 | Interactive wizard to create `config.yml` and folder structure from scratch |
|
|
||||||
| 3 | Scan `pages/` and `posts/`, write `nav.yml` and `search.json` |
|
|
||||||
| 4 | Rebuild `search.json` only (faster, for content-only updates) |
|
|
||||||
| 5 | Check and interactively fix `config.yml` issues |
|
|
||||||
| 6 | Check and interactively fix `nav.yml` issues |
|
|
||||||
| 7 | Register/switch website paths (stored in `~/.mdcms/paths.json`) |
|
|
||||||
| 8 | Start `python3 -m http.server 8800` and open browser |
|
|
||||||
|
|
||||||
**Local preview:** Always use option 8 (or run `python3 -m http.server 8800` in `website/`) rather than opening `index.html` directly — browsers block local file access due to CORS.
|
During development, run directly: `python3 mdcms.py <command>`
|
||||||
|
|
||||||
|
| Command | Description |
|
||||||
|
|---|---|
|
||||||
|
| `mdcms register <name> [path]` | Register a site. Downloads starter template from GitHub if no mdcms site is found at the path. Defaults to current directory. |
|
||||||
|
| `mdcms delete <name>` | Remove a site from the registry. Does not delete files. Prompts for confirmation. |
|
||||||
|
| `mdcms view` | List all registered sites with version and status. |
|
||||||
|
| `mdcms view <name>` | Show details: path, version, sitename, pages/posts count, sections, categories. |
|
||||||
|
| `mdcms build <name>` | Build `nav.yml` and `search.json` for a registered site. |
|
||||||
|
| `mdcms build --path <path>` | Build using an explicit path — no registry needed. Intended for CI/CD. |
|
||||||
|
| `mdcms build` | Build using current working directory. Simplest form for GitHub Actions. |
|
||||||
|
|
||||||
|
**Local preview:** Run `python3 -m http.server 8800` in the site directory and open `http://localhost:8800`. Do not open `index.html` directly — browsers block local file access due to CORS.
|
||||||
|
|
||||||
## Architecture of `mdcms.py`
|
## Architecture of `mdcms.py`
|
||||||
|
|
||||||
The file is a single-module Python script with these logical layers, in order:
|
Single-module Python script. Logical layers in order:
|
||||||
|
|
||||||
1. **Path registry** — `~/.mdcms/paths.json` stores named project paths. `active_path()` returns the currently selected project.
|
1. **Version helpers** — `read_site_version()` reads the `mdcms v0.3` marker from the first line of `config.yml`. `version_status()` classifies sites as `ok`, `outdated`, `newer`, or `unsupported` against `MIN_SUPPORTED_VERSION`.
|
||||||
2. **Frontmatter parser** (`parse_frontmatter`) — reads `---` YAML blocks with a hand-rolled parser (no PyYAML). Returns a flat `{key: value}` dict with type coercion (bool, int, quoted strings).
|
2. **Registry** — `~/.config/mdcms/sites.json` stores `{name: {path, version}}`. `load_registry()` / `save_registry()` / `resolve_site_path()`.
|
||||||
3. **Category system** — category codes are parsed from `config.yml` by `parse_config_categories()`. Variant files use `basename.<code>.md` naming (e.g. `about.nb.md`). `identify_variant()` only recognises a suffix as a category code if it appears in the declared code list.
|
3. **Config reading** — `read_config()` reads `config.yml` with `yaml.safe_load()`. `get_category_info()` extracts category settings from the parsed dict.
|
||||||
4. **Scanner** (`scan_and_categorize`) — walks `pages/` or `posts/`, skips drafts (`draft: true` in frontmatter), returns records including first 5000 chars of body for search indexing.
|
4. **Frontmatter parser** (`parse_frontmatter`) — reads `---` YAML blocks using `yaml.safe_load()`. Returns `(meta_dict, body_text)`.
|
||||||
5. **Nav/search generators** — `generate_nav_yml()` and `generate_search_json()` produce the output files. `parse_nav_yml()` is a hand-rolled parser for reading back nav.yml (not a general YAML parser — only handles the exact format it emits).
|
5. **Category system** — `identify_variant()` splits `.md` paths into `(base, category_code)`. A suffix is only treated as a category code if it appears in the declared code list.
|
||||||
6. **Validators** (`validate_config`, `validate_nav`) — return lists of `{field, issue, current}` dicts for display; used in options 1, 5, and 6.
|
6. **Scanner** (`scan_and_categorize`) — walks a directory, skips drafts, returns records with the first 5000 chars of body for search indexing. Paths are relative to `site_root`.
|
||||||
7. **Menu actions** (`do_*` functions) — one per menu option, called by `main_menu()`.
|
7. **Nav/search generators** — `generate_nav_yml()` emits a fixed-format YAML subset. `generate_search_json()` emits a JSON array. `merge_sections()` preserves existing section metadata on rebuild.
|
||||||
|
8. **Core build** (`run_build`) — orchestrates the full build: version check → config read → scan → merge → write nav.yml and search.json.
|
||||||
|
9. **Template download** (`download_template`) — fetches `app/` from GitHub via the Contents API using `urllib` (no extra dependencies). Recursively downloads files and directories.
|
||||||
|
10. **CLI commands** (`register`, `delete`, `view`, `build`) — implemented with `click`. Entry point: `main()` → `cli()`.
|
||||||
|
|
||||||
## Content structure inside `website/`
|
## Version markers
|
||||||
|
|
||||||
|
Every mdcms site has a version marker on the first line of two files:
|
||||||
|
|
||||||
|
- `config.yml` line 1: `# mdcms v0.3 | DO NOT REMOVE THIS COMMENT`
|
||||||
|
- `index.html` line 1: `<!-- mdcms v0.3 | DO NOT REMOVE THIS COMMENT -->`
|
||||||
|
|
||||||
|
`register` and `build` both read the marker from `config.yml` to detect and validate the site. Sites with no marker are not recognised as mdcms sites. Sites below `MIN_SUPPORTED_VERSION` are rejected.
|
||||||
|
|
||||||
|
There are two distinct version numbers:
|
||||||
|
- **CLI version** (`CLI_VERSION` in `mdcms.py`, `version` in `pyproject.toml`) — bumped with every release.
|
||||||
|
- **Site format version** (markers in `config.yml` and `index.html`) — only bumped when the site file format has a breaking change. Many CLI releases may share the same site format version.
|
||||||
|
|
||||||
|
## Site structure
|
||||||
|
|
||||||
|
The registered path points directly to the directory containing `index.html` (the site root). There is no `website/` subdirectory.
|
||||||
|
|
||||||
```
|
```
|
||||||
website/
|
<site-root>/
|
||||||
index.html ← renderer (edit to change site behaviour)
|
index.html ← renderer
|
||||||
config.yml ← required: sitename, navigation; rest optional
|
config.yml ← required: sitename, navigation; rest optional
|
||||||
nav.yml ← generated; manual edits to section metadata are preserved on rebuild
|
nav.yml ← generated; manual edits to section metadata are preserved
|
||||||
search.json ← generated
|
search.json ← generated
|
||||||
pages/
|
pages/
|
||||||
home.md ← default landing page
|
home.md ← default landing page
|
||||||
about.md ← default variant
|
about.md
|
||||||
about.nb.md ← Norwegian variant (category suffix = nb)
|
about.nb.md ← Norwegian variant (category suffix = nb)
|
||||||
posts/
|
posts/
|
||||||
2025-01-01-my-first-post.md
|
2025-01-01-my-first-post.md
|
||||||
|
|
@ -113,15 +150,35 @@ paginate: yes
|
||||||
|
|
||||||
Reliable tags (others are known-broken): `posts-datetime-chronological-byyearmonth`, `posts-datetime-reversechronological`. Use `datetime` frontmatter (format: `YYYY-MM-DD HH:MM`) for posts — `date` alone does not work reliably.
|
Reliable tags (others are known-broken): `posts-datetime-chronological-byyearmonth`, `posts-datetime-reversechronological`. Use `datetime` frontmatter (format: `YYYY-MM-DD HH:MM`) for posts — `date` alone does not work reliably.
|
||||||
|
|
||||||
|
## Release workflow
|
||||||
|
|
||||||
|
`.github/workflows/release.yml` triggers on version tags (`v*`). Uses a matrix of three runners:
|
||||||
|
|
||||||
|
| Runner | Output |
|
||||||
|
|---|---|
|
||||||
|
| `ubuntu-latest` | `mdcms-linux-amd64` binary + `mdcms_<version>_amd64.deb` (via PyInstaller + fpm) |
|
||||||
|
| `macos-latest` | `mdcms-macos-arm64` binary |
|
||||||
|
| `windows-latest` | `mdcms-windows-amd64.exe` |
|
||||||
|
|
||||||
|
All artifacts are attached to the GitHub release using `gh release create`.
|
||||||
|
|
||||||
|
**Release checklist** — before tagging:
|
||||||
|
1. Update `CLI_VERSION` in `mdcms.py`
|
||||||
|
2. Update `version` in `pyproject.toml`
|
||||||
|
3. Update site format markers in `app/config.yml` and `app/index.html` only if the site format changed
|
||||||
|
|
||||||
|
Then: `git tag v0.3.1 && git push origin v0.3.1`
|
||||||
|
|
||||||
## Known limitations
|
## Known limitations
|
||||||
|
|
||||||
- `mdcms.py` always expects the deployable site in a `website/` subdirectory of the registered project path.
|
|
||||||
- Most `posts-*` tag variants are broken. Only `posts-datetime-chronological-byyearmonth` and `posts-datetime-reversechronological` reliably work.
|
- Most `posts-*` tag variants are broken. Only `posts-datetime-chronological-byyearmonth` and `posts-datetime-reversechronological` reliably work.
|
||||||
- Section headings in the nav are non-clickable (sections-sitemap is not yet implemented).
|
- Section headings in the nav are non-clickable (sections-sitemap is not yet implemented).
|
||||||
|
|
||||||
## Key implementation details
|
## Key implementation details
|
||||||
|
|
||||||
- `parse_nav_yml()` and `parse_config_categories()` are **not** general YAML parsers — they only handle the exact indentation/format that `mdcms.py` itself produces. Do not assume they handle arbitrary YAML.
|
- `generate_nav_yml()` emits a fixed-format YAML subset. It is **not** a general YAML emitter — do not assume it handles arbitrary structures.
|
||||||
|
- `yaml.safe_load()` is used for all YAML reading (config.yml, nav.yml, frontmatter). The nav.yml parser depends on PyYAML, not a hand-rolled parser.
|
||||||
- Category code validation uses `CATEGORY_CODE_RE = re.compile(r"^[a-zA-Z0-9\-]+$")` — codes must match this.
|
- Category code validation uses `CATEGORY_CODE_RE = re.compile(r"^[a-zA-Z0-9\-]+$")` — codes must match this.
|
||||||
|
- `scan_and_categorize()` takes both `directory` and `site_root` — paths in records are always relative to `site_root`.
|
||||||
- The `samplesite/` directory is a reference implementation with multi-language categories (English, Norwegian, Arabic including RTL). It is not deployed; it exists for reference and testing.
|
- The `samplesite/` directory is a reference implementation with multi-language categories (English, Norwegian, Arabic including RTL). It is not deployed; it exists for reference and testing.
|
||||||
- `website/` in the repo root is a minimal starter template (single page, no categories).
|
- Template download uses `urllib` (stdlib) only — no `requests` dependency.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
# MD-CMS v0.2 — Site configuration
|
# mdcms v0.3 | DO NOT REMOVE THIS COMMENT
|
||||||
|
# MD-CMS v0.3 — Site configuration
|
||||||
#
|
#
|
||||||
# Only `sitename` and `navigation` are required. Uncomment and edit the rest
|
# Only `sitename` and `navigation` are required. Uncomment and edit the rest
|
||||||
# as needed. See https://kbenestad.codeberg.page/md-cms for the full reference.
|
# as needed. See https://kbenestad.codeberg.page/md-cms for the full reference.
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
|
<!-- mdcms v0.3 | DO NOT REMOVE THIS COMMENT -->
|
||||||
<!--
|
<!--
|
||||||
MD-CMS v0.2 — Renderer
|
MD-CMS v0.3 — Renderer
|
||||||
|
|
||||||
Copyright 2026 Kristian Benestad | kbenestad.codeberg.page
|
Copyright 2026 Kristian Benestad | kbenestad.codeberg.page
|
||||||
|
|
||||||
161
docs/install.md
Normal file
161
docs/install.md
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
# Setting up mdcms for your site
|
||||||
|
|
||||||
|
This guide walks through installing mdcms, registering your site, and setting up automatic builds on GitHub so that `nav.yml` and `search.json` are always up to date when you push content.
|
||||||
|
|
||||||
|
## 1. Install mdcms
|
||||||
|
|
||||||
|
Choose one method:
|
||||||
|
|
||||||
|
**Standalone binary (no Python required — recommended for most users)**
|
||||||
|
|
||||||
|
Download the binary for your platform from the [latest release](https://github.com/kbenestad/mdcms/releases/latest):
|
||||||
|
|
||||||
|
- **Linux:** `mdcms-linux-amd64` → move to `/usr/local/bin/mdcms` and `chmod +x`
|
||||||
|
- **Linux (deb):** `mdcms_<version>_amd64.deb` → `sudo dpkg -i mdcms_<version>_amd64.deb`
|
||||||
|
- **macOS:** `mdcms-macos-arm64` → move to `/usr/local/bin/mdcms` and `chmod +x`
|
||||||
|
- **Windows:** `mdcms-windows-amd64.exe` → rename to `mdcms.exe` and place it somewhere on your PATH
|
||||||
|
|
||||||
|
**Via pipx (Python users)**
|
||||||
|
```bash
|
||||||
|
pipx install mdcms
|
||||||
|
```
|
||||||
|
|
||||||
|
Verify the installation:
|
||||||
|
```bash
|
||||||
|
mdcms --version
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Register your site
|
||||||
|
|
||||||
|
Navigate to your site directory (where `index.html` lives) and register it:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/your/site
|
||||||
|
mdcms register mysite
|
||||||
|
```
|
||||||
|
|
||||||
|
Or pass the path explicitly:
|
||||||
|
```bash
|
||||||
|
mdcms register mysite /path/to/your/site
|
||||||
|
```
|
||||||
|
|
||||||
|
**If no mdcms site exists at that path**, mdcms will download the starter template from GitHub automatically — `index.html`, `config.yml`, `pages/`, `posts/`, and `assets/` will be created for you.
|
||||||
|
|
||||||
|
**If a site already exists** (you cloned an existing mdcms repo), mdcms will detect the version marker in `config.yml` and register it as-is.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Edit your config
|
||||||
|
|
||||||
|
Open `config.yml` in your site directory and set at minimum:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sitename: Your Site Name
|
||||||
|
navigation: topbar # or: sidebar
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Build locally
|
||||||
|
|
||||||
|
Run a build to generate `nav.yml` and `search.json` from your content:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mdcms build mysite
|
||||||
|
```
|
||||||
|
|
||||||
|
Check your site by starting a local server:
|
||||||
|
```bash
|
||||||
|
cd /path/to/your/site
|
||||||
|
python3 -m http.server 8800
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open `http://localhost:8800` in your browser.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Set up automatic builds on GitHub
|
||||||
|
|
||||||
|
When you push new pages or posts to GitHub, you'll want `nav.yml` and `search.json` rebuilt automatically. This is done with a simple GitHub Actions workflow.
|
||||||
|
|
||||||
|
### One-time GitHub setup
|
||||||
|
|
||||||
|
1. Go to your site repository on GitHub
|
||||||
|
2. **Settings → Actions → General**
|
||||||
|
3. Under **Workflow permissions**, select **Read and write permissions**
|
||||||
|
4. Click **Save**
|
||||||
|
|
||||||
|
This allows the workflow to commit the rebuilt files back to your repository.
|
||||||
|
|
||||||
|
### Add the workflow file
|
||||||
|
|
||||||
|
Create `.github/workflows/build.yml` in your site repository with this content:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
paths:
|
||||||
|
- "pages/**"
|
||||||
|
- "posts/**"
|
||||||
|
- "config.yml"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.12"
|
||||||
|
|
||||||
|
- name: Install mdcms
|
||||||
|
run: pip install mdcms
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: mdcms build
|
||||||
|
|
||||||
|
- name: Commit updated files
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git add nav.yml search.json
|
||||||
|
git diff --staged --quiet || git commit -m "Build: update nav.yml and search.json"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
Commit and push this file to your repository. From that point on, every push that touches `pages/`, `posts/`, or `config.yml` will automatically rebuild and commit `nav.yml` and `search.json`.
|
||||||
|
|
||||||
|
> The workflow only commits if the files actually changed — `git diff --staged --quiet` skips the commit if nothing is different.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Day-to-day workflow
|
||||||
|
|
||||||
|
1. Write or edit a page in `pages/` or a post in `posts/`
|
||||||
|
2. Commit and push to `main`
|
||||||
|
3. GitHub Actions rebuilds `nav.yml` and `search.json` automatically (~30 seconds)
|
||||||
|
4. Your deployed site picks up the changes immediately
|
||||||
|
|
||||||
|
To build locally at any time (without pushing):
|
||||||
|
```bash
|
||||||
|
mdcms build mysite
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Useful commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mdcms view # list all registered sites
|
||||||
|
mdcms view mysite # show details for a site
|
||||||
|
mdcms build mysite # build nav.yml + search.json
|
||||||
|
mdcms build --path . # build from current directory (no registry needed)
|
||||||
|
mdcms delete mysite # remove a site from the local registry
|
||||||
|
```
|
||||||
92
docs/release.md
Normal file
92
docs/release.md
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
# Releasing a new version of mdcms
|
||||||
|
|
||||||
|
This guide covers publishing a new mdcms release — producing binaries, a `.deb` package, and a GitHub release with all artifacts attached.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- Push access to `kbenestad/mdcms` on GitHub
|
||||||
|
- GitHub Actions enabled on the repository (it is by default on new repos)
|
||||||
|
|
||||||
|
## One-time GitHub setup
|
||||||
|
|
||||||
|
### Enable workflow write permissions
|
||||||
|
|
||||||
|
The release workflow creates a GitHub release using the built-in `GITHUB_TOKEN`. You need to confirm it has write access:
|
||||||
|
|
||||||
|
1. Go to your repository on GitHub
|
||||||
|
2. **Settings → Actions → General**
|
||||||
|
3. Scroll to **Workflow permissions**
|
||||||
|
4. Select **Read and write permissions**
|
||||||
|
5. Click **Save**
|
||||||
|
|
||||||
|
That's the only setup required. No secrets, no tokens, no third-party services.
|
||||||
|
|
||||||
|
## The release checklist
|
||||||
|
|
||||||
|
Before tagging a release, update the version number in two places:
|
||||||
|
|
||||||
|
**`mdcms.py`** — find this line near the top and bump it:
|
||||||
|
```python
|
||||||
|
CLI_VERSION = "0.3.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
**`pyproject.toml`** — bump the matching line:
|
||||||
|
```toml
|
||||||
|
version = "0.3.1"
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Site format version** — the markers in `app/config.yml` and `app/index.html` (`# mdcms v0.3`) are separate from the CLI version. Only update them if the site file format has a breaking change that requires users to re-download the template. Most releases do not touch these.
|
||||||
|
|
||||||
|
Commit the version bump:
|
||||||
|
```bash
|
||||||
|
git add mdcms.py pyproject.toml
|
||||||
|
git commit -m "Bump version to 0.3.1"
|
||||||
|
git push origin main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tagging the release
|
||||||
|
|
||||||
|
Push a version tag to trigger the workflow:
|
||||||
|
```bash
|
||||||
|
git tag v0.3.1
|
||||||
|
git push origin v0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
The tag must start with `v`. The workflow triggers immediately.
|
||||||
|
|
||||||
|
> **Pre-releases** — if the tag contains a hyphen (e.g. `v0.4.0-beta.1`), the GitHub release is automatically marked as pre-release.
|
||||||
|
|
||||||
|
## What the workflow does
|
||||||
|
|
||||||
|
The workflow (`.github/workflows/release.yml`) runs three parallel jobs:
|
||||||
|
|
||||||
|
| Runner | Output |
|
||||||
|
|---|---|
|
||||||
|
| `ubuntu-latest` | `mdcms-linux-amd64` (standalone binary) + `mdcms_0.3.1_amd64.deb` |
|
||||||
|
| `macos-latest` | `mdcms-macos-arm64` (standalone binary) |
|
||||||
|
| `windows-latest` | `mdcms-windows-amd64.exe` |
|
||||||
|
|
||||||
|
Each binary is built with PyInstaller — Python is bundled inside, so end users need nothing pre-installed. The `.deb` is built with `fpm` and installs mdcms to `/usr/local/bin`.
|
||||||
|
|
||||||
|
A final job collects all artifacts and creates the GitHub release with auto-generated release notes.
|
||||||
|
|
||||||
|
## Monitoring the build
|
||||||
|
|
||||||
|
1. Go to **Actions** on the repository
|
||||||
|
2. Click the workflow run triggered by your tag
|
||||||
|
3. Each platform job takes roughly 3–5 minutes
|
||||||
|
|
||||||
|
If a job fails, the release is not created. Fix the issue, delete the tag, and re-push:
|
||||||
|
```bash
|
||||||
|
git tag -d v0.3.1
|
||||||
|
git push origin --delete v0.3.1
|
||||||
|
# fix the issue, then re-tag
|
||||||
|
git tag v0.3.1
|
||||||
|
git push origin v0.3.1
|
||||||
|
```
|
||||||
|
|
||||||
|
## The finished release
|
||||||
|
|
||||||
|
Once all jobs pass, the release appears under **Releases** on the repository with:
|
||||||
|
- Auto-generated changelog (commits since the last tag)
|
||||||
|
- All four artifacts attached for download
|
||||||
26
pyproject.toml
Normal file
26
pyproject.toml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=68", "wheel"]
|
||||||
|
build-backend = "setuptools.backends.legacy:build"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "mdcms"
|
||||||
|
version = "0.3.0"
|
||||||
|
description = "MD-CMS — Markdown-based CMS companion CLI"
|
||||||
|
readme = "README.md"
|
||||||
|
license = { text = "Apache-2.0" }
|
||||||
|
authors = [{ name = "Kristian Benestad" }]
|
||||||
|
requires-python = ">=3.9"
|
||||||
|
dependencies = [
|
||||||
|
"click>=8.0",
|
||||||
|
"PyYAML>=6.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
mdcms = "mdcms:main"
|
||||||
|
|
||||||
|
[project.urls]
|
||||||
|
Homepage = "https://github.com/kbenestad/mdcms"
|
||||||
|
Documentation = "https://docs.benestad.net"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
py-modules = ["mdcms"]
|
||||||
Loading…
Reference in a new issue