diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 43a25ed..2126d5b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,2 +1,7 @@ -# Default owners for everything in the repo -* @backstage/techdocs-maintainers +# This file registers ownership for certain parts of the backstage code. +# Review from a member of the corresponding code owner is required to merge pull requests. +# +# The last matching pattern takes precedence. +# https://help.github.com/articles/about-codeowners/ + +* @backstage/techdocs-maintainers diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..7dce19b --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,65 @@ +name: CI + +on: + pull_request: + branches: [main] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Harden Runner + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - name: Build (no push) + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: . + push: false + platforms: linux/amd64 + + - name: Build image for testing + run: docker build -t techdocs-zensical-test . + + - name: Verify zensical binary + run: docker run --rm techdocs-zensical-test --version + + - name: Verify Python Markdown extensions are importable + run: | + docker run --rm --entrypoint python techdocs-zensical-test -c "import plantuml_markdown; print('plantuml_markdown OK')" + docker run --rm --entrypoint python techdocs-zensical-test -c "import markdown_inline_graphviz; print('markdown_inline_graphviz OK')" + docker run --rm --entrypoint python techdocs-zensical-test -c "import mdx_truly_sane_lists; print('mdx_truly_sane_lists OK')" + + - name: Verify system dependencies + run: | + docker run --rm --entrypoint sh techdocs-zensical-test -c "which graphviz || dot -V" + docker run --rm --entrypoint sh techdocs-zensical-test -c "which plantuml || plantuml -version" + + - name: Build test docs + run: | + docker run --rm \ + -v ${{ github.workspace }}/test-docs:/input \ + -w /input \ + techdocs-zensical-test build + # Verify output was generated + test -f test-docs/site/index.html || (echo "index.html not found" && exit 1) + test -f test-docs/site/extensions/index.html || (echo "extensions page not found" && exit 1) + echo "Build output verified" + + - name: Verify classic mode CSS classes preserved + run: | + grep -q 'class="md-content"' test-docs/site/index.html || (echo "Missing .md-content class" && exit 1) + grep -q 'class="md-sidebar' test-docs/site/index.html || (echo "Missing .md-sidebar class" && exit 1) + grep -q 'data-md-component' test-docs/site/index.html || (echo "Missing data-md-component" && exit 1) + echo "Classic mode CSS classes verified" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af75826..edaab21 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,59 +1,56 @@ -name: Release Docker Image +name: Build and publish container image on: push: - tags: - - 'v*' - workflow_dispatch: - inputs: - tag: - description: 'Tag to release (e.g., v1.0.0)' - required: true - type: string - -env: - REGISTRY: ghcr.io - IMAGE_NAME: backstage/techdocs-zensical + branches: [main] + release: + types: [published] + +permissions: + contents: read + packages: write jobs: - release: - if: false # Disabled until ready for release + publish: runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 + - name: Harden Runner + uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Set up QEMU + uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 - - name: Log in to Container Registry - uses: docker/login-action@v3 + - name: Login to GitHub Container Registry + uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 with: - registry: ${{ env.REGISTRY }} + registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=semver,pattern={{major}} - type=raw,value=latest,enable={{is_default_branch}} + - name: Extract version from release tag + id: version + run: | + if [ "${{ github.event_name }}" = "release" ]; then + echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + fi - - name: Build and push Docker image - uses: docker/build-push-action@v5 + - name: Build and push + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 with: context: . - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max platforms: linux/amd64,linux/arm64 + push: true + tags: | + ghcr.io/${{ github.repository_owner }}/techdocs-zensical:latest + ${{ steps.version.outputs.tag && format('ghcr.io/{0}/techdocs-zensical:{1}', github.repository_owner, steps.version.outputs.tag) || '' }} + labels: | + org.opencontainers.image.description=TechDocs container image for building documentation with Zensical. Includes PlantUML, Graphviz, and additional Python Markdown extensions. + org.opencontainers.image.source=https://github.com/${{ github.repository }} diff --git a/Dockerfile b/Dockerfile index 0d8aade..4190151 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,33 @@ -FROM python:3.14-alpine@sha256:dd4d2bd5b53d9b25a51da13addf2be586beebd5387e289e798e4083d94ca837a +# Copyright 2026 The Backstage Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. -WORKDIR /input +# Base on the official Zensical image which already includes: +# - Python, Zensical, markdown, pymdown-extensions, pygments +FROM zensical/zensical:0.0.69 +# System dependencies for PlantUML and Graphviz diagram rendering RUN apk update && \ apk --no-cache add bash graphviz plantuml && \ rm -rf /var/cache/apk/* +# Additional Python Markdown extensions not bundled by Zensical. +# These hook into Python Markdown's processing pipeline. COPY requirements.txt /requirements.txt RUN pip install --no-cache-dir -r /requirements.txt -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +WORKDIR /input -ENTRYPOINT ["/entrypoint.sh"] +# Config preprocessing is handled by TechdocsZensicalGenerator in TypeScript. +# This container just runs the zensical binary. +ENTRYPOINT ["zensical"] diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index 6fdc31c..0000000 --- a/entrypoint.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -set -e - -# Pass through to zensical directly -# Config preprocessing is handled by Backstage/techdocs-cli in TypeScript -exec zensical "$@" diff --git a/requirements.txt b/requirements.txt index 348cdfb..3ece7d3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,18 +1,10 @@ -# Zensical static site generator (replaces mkdocs + mkdocs-material) -zensical==0.0.40 - -# Config processing -PyYAML - -# Markdown (Zensical uses Python Markdown under the hood) -Markdown>=3.2,<3.11 - -# Markdown extensions - pinned versions matching mkdocs-techdocs-core -markdown-graphviz-inline==1.1.3 +# Third-party Python Markdown extensions not bundled by Zensical. +# These hook into the Python Markdown processing pipeline that +# Zensical uses under the hood. plantuml-markdown==3.11.2 +markdown-graphviz-inline==1.1.3 mdx_truly_sane_lists==1.3 -pymdown-extensions==10.21.2 -pygments==2.20.0 # Transitive dependencies for plantuml-markdown requests +urllib3 diff --git a/test-docs/.gitignore b/test-docs/.gitignore new file mode 100644 index 0000000..45ddf0a --- /dev/null +++ b/test-docs/.gitignore @@ -0,0 +1 @@ +site/ diff --git a/test-docs/docs/extensions.md b/test-docs/docs/extensions.md new file mode 100644 index 0000000..6a2830a --- /dev/null +++ b/test-docs/docs/extensions.md @@ -0,0 +1,20 @@ +# Extension Tests + +## Graphviz (inline) + +```graphviz +digraph G { + A -> B -> C; +} +``` + +## Truly Sane Lists + +- Item 1 + - Nested with 2-space indent + - Should render correctly +- Item 2 + +## Emoji + +:thumbsup: :rocket: diff --git a/test-docs/docs/index.md b/test-docs/docs/index.md new file mode 100644 index 0000000..5f91d9b --- /dev/null +++ b/test-docs/docs/index.md @@ -0,0 +1,29 @@ +# Test Page + +Basic documentation page to verify the Zensical container works. + +## Admonitions + +!!! note + This is an admonition. + +## Tabs + +=== "Tab 1" + Content for tab 1. + +=== "Tab 2" + Content for tab 2. + +## Code highlighting + +```python +def hello(): + print("Hello from Zensical!") +``` + +## Task list + +- [x] Build container +- [x] Verify extensions +- [ ] Ship it diff --git a/test-docs/mkdocs.yml b/test-docs/mkdocs.yml new file mode 100644 index 0000000..899a7e3 --- /dev/null +++ b/test-docs/mkdocs.yml @@ -0,0 +1,4 @@ +site_name: Zensical Test Docs +nav: + - Home: index.md + - Extensions: extensions.md