diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8ce406..642a957 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: CI +name: Continuous Integration on: push: @@ -11,72 +11,150 @@ on: permissions: {} +env: + PYTHON_VERSION: "3.10" + jobs: + determine_changes: + name: "determine changes" + + runs-on: ubuntu-latest + + outputs: + # Flag that is raised when any code is changed + code: ${{ steps.check_code.outputs.changed }} + + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Determine merge base + id: merge_base + env: + BASE_REF: ${{ github.event.pull_request.base.ref || 'main' }} + run: | + sha=$(git merge-base HEAD "origin/${BASE_REF}") + echo "sha=${sha}" >> "$GITHUB_OUTPUT" + + - name: Check if there was any code related change + id: check_code + env: + MERGE_BASE: ${{ steps.merge_base.outputs.sha }} + run: | + if git diff --quiet "${MERGE_BASE}...HEAD" -- \ + . \ + ':!docs/' \ + ':!CODE_OF_CONDUCT.md' \ + ':!CONTRIBUTING.md' \ + ':!LICENSE' \ + ':!pyproject.toml' \ + ':!README.md' \ + ':!zensical.toml' \ + ; then + echo "changed=false" >> "$GITHUB_OUTPUT" + else + echo "changed=true" >> "$GITHUB_OUTPUT" + fi + pre-commit: - name: pre-commit + name: "pre commit" + runs-on: ubuntu-latest steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false - uses: j178/prek-action@91fd7d7cf70ae1dee9f4f44e7dfa5d1073fe6623 # v1.0.11 + build-docs: + name: "Build docs" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + + - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Prepare docs + run: uv run --script scripts/prepare_docs.py + + - name: Build docs + run: uv run --isolated --with-requirements docs/requirements.txt zensical build + cargo-test: - name: cargo test (${{ matrix.os }}) + name: "cargo test (${{ matrix.os }})" + runs-on: ${{ matrix.os }} + needs: determine_changes + + if: ${{ (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} + strategy: - max-parallel: 4 + max-parallel: 18 fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - - name: Install Rust toolchain + - name: "Install Rust toolchain" run: rustup show - - name: Install cargo-nextest - uses: taiki-e/install-action@90558ad1e179036f31467972b00dec6cb80701fa # v2.66.3 + - name: "Install cargo nextest" + uses: taiki-e/install-action@a416ddeedbd372e614cc1386e8b642692f66865e # v2.57.1 with: tool: cargo-nextest - - name: Run tests - run: cargo nextest run --no-fail-fast --all-features + - name: "Cargo test" + run: | + cargo nextest run --no-fail-fast --all-features coverage: - name: coverage + name: "coverage" + runs-on: ubuntu-latest + needs: determine_changes + + if: ${{ (needs.determine_changes.outputs.code == 'true' || github.ref == 'refs/heads/main') }} + steps: - - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 with: persist-credentials: false - uses: Swatinem/rust-cache@779680da715d629ac1d338a641029a2f4372abb5 # v2.8.2 - - name: Install Rust toolchain + - name: "Install Rust toolchain" run: rustup show - - name: Install coverage tools - uses: taiki-e/install-action@90558ad1e179036f31467972b00dec6cb80701fa # v2.66.3 + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@62da238c048aa0f865cc5a322082957d34e7fc1a # v2.62.54 with: tool: cargo-llvm-cov,cargo-nextest - - name: Generate coverage + - name: Generate code coverage run: cargo llvm-cov nextest --all-features --lcov --output-path lcov.info - - name: Upload coverage - uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2 + - name: Upload coverage to Codecov + uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1 with: token: ${{ secrets.CODECOV_TOKEN }} files: lcov.info - slug: MatthewMckee4/action-format + slug: MatthewMckee4/seal fail_ci_if_error: true diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml new file mode 100644 index 0000000..6a2794a --- /dev/null +++ b/.github/workflows/publish-docs.yml @@ -0,0 +1,71 @@ +name: Deploy Documentation + +on: + workflow_dispatch: + inputs: + ref: + description: "The commit SHA, tag, or branch to publish. Uses the default branch if not specified." + default: "" + type: string + + workflow_call: + inputs: + plan: + required: true + type: string + +concurrency: + group: "pages" + cancel-in-progress: false + +permissions: {} + +env: + PYTHON_VERSION: "3.10" + +jobs: + build: + name: "Build docs" + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + with: + persist-credentials: false + + - uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2 + with: + python-version: ${{ env.PYTHON_VERSION }} + + - name: Prepare docs + run: uv run --script scripts/prepare_docs.py + + - name: Build docs + run: uv run --isolated --with-requirements docs/requirements.txt zensical build + + - name: Upload artifact + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 + with: + path: ./site + + deploy: + runs-on: ubuntu-latest + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + permissions: + contents: read + pages: write + id-token: write + + needs: build + + if: github.ref == 'refs/heads/main' + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5 diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..fc0ab8a --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1 @@ +index.md diff --git a/scripts/prepare_docs.py b/scripts/prepare_docs.py new file mode 100644 index 0000000..6c8571d --- /dev/null +++ b/scripts/prepare_docs.py @@ -0,0 +1,42 @@ +# /// script +# requires-python = ">=3.13" +# dependencies = [] +# /// + +import re +from pathlib import Path + +ROOT = Path(__file__).parent.parent + +TAB = " " + + +def prepare_index_file() -> None: + """Copy root `README.md` to `docs/index.md`""" + (ROOT / "docs" / "index.md").write_text((ROOT / "README.md").read_text()) + + +def convert_markdown_admonitions(path: Path) -> None: + """Convert GitHub-style alerts to MkDocs-style admonitions""" + content = path.read_text() + + def replace_alert(match: re.Match[str]) -> str: + alert_type = match.group(1).lower() + lines = match.group(2).strip().split("\n") + body_lines = [line.lstrip("> ").rstrip() for line in lines if line.strip()] + body = f"\n{TAB}".join(body_lines) + return f"!!! {alert_type}{TAB}{body}\n" + + pattern = r"> \[!(WARNING|NOTE|TIP|IMPORTANT|CAUTION)\]\n((?:>\s*.*\n?)*)" + content = re.sub(pattern, replace_alert, content) + + path.write_text(content) + + +def main() -> None: + prepare_index_file() + convert_markdown_admonitions(ROOT / "docs" / "index.md") + + +if __name__ == "__main__": + main()