-
Notifications
You must be signed in to change notification settings - Fork 0
Add install-nixie shared action #360
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| # Changelog | ||
|
|
||
| ## v1.0.0 (Unreleased) | ||
|
|
||
| - Add pinned Nixie and Merman CLI installation. | ||
| - Prefer `cargo binstall` for Merman and fall back to a locked source build. | ||
| - Support version overrides for Nixie, Merman CLI, and Python. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| # Install Nixie | ||
|
|
||
| Install pinned Nixie and Merman CLI releases for Mermaid validation. | ||
|
|
||
| The action installs Nixie through `uv` and its Merman rendering backend through | ||
| Cargo. It uses `cargo binstall` when available and falls back to a locked | ||
| `cargo install` build from crates.io. | ||
|
|
||
| ## Inputs | ||
|
|
||
| | Name | Description | Required | Default | | ||
| | ---------------- | ---------------------------------------- | -------- | ------- | | ||
| | `nixie-version` | Nixie CLI version to install | no | `1.1.0` | | ||
| | `merman-version` | Merman CLI version to install | no | `0.7.0` | | ||
| | `python-version` | Python version used to install Nixie | no | `3.14` | | ||
|
|
||
| ## Outputs | ||
|
|
||
| | Name | Description | | ||
| | ------ | ------------------------------------------------------- | | ||
| | _None_ | The action emits no outputs. | | ||
|
|
||
| ## Usage | ||
|
|
||
| ```yaml | ||
| - name: Set up Rust | ||
| uses: leynos/shared-actions/.github/actions/setup-rust@v1 | ||
|
|
||
| - name: Install Nixie | ||
| uses: leynos/shared-actions/.github/actions/install-nixie@v1 | ||
|
|
||
| - name: Validate Mermaid diagrams | ||
| run: nixie --renderer merman | ||
| ``` | ||
|
|
||
| To override the pinned versions: | ||
|
|
||
| ```yaml | ||
| - uses: leynos/shared-actions/.github/actions/install-nixie@v1 | ||
| with: | ||
| nixie-version: "1.1.0" | ||
| merman-version: "0.7.0" | ||
| python-version: "3.14" | ||
| ``` | ||
|
|
||
| ## Behaviour | ||
|
|
||
| - **Prerequisites**: `cargo` and `uv` must already be available on `PATH`. | ||
| The repository's `setup-rust` action provisions both tools and | ||
| `cargo-binstall`. | ||
| - **Merman installation**: When `cargo binstall` is available, the action | ||
| installs the selected Merman release from a binary package with locked | ||
| metadata. Otherwise it builds the exact selected release from crates.io with | ||
| `cargo install --locked`. | ||
| - **Nixie installation**: The action uses `uv tool install` with the selected | ||
| Python and exact Nixie release. | ||
| - **Failure behaviour**: Missing prerequisites and failed installations stop | ||
| the action immediately with a non-zero exit status. | ||
|
|
||
| ## Release history | ||
|
|
||
| See [CHANGELOG](CHANGELOG.md). | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| name: Install Nixie | ||
| description: Install pinned Nixie and Merman CLI releases for Mermaid validation | ||
| inputs: | ||
| nixie-version: | ||
| description: Nixie CLI version to install | ||
| required: false | ||
| default: "1.1.0" | ||
| merman-version: | ||
| description: Merman CLI version to install | ||
| required: false | ||
| default: "0.7.0" | ||
| python-version: | ||
| description: Python version used by uv to install Nixie | ||
| required: false | ||
| default: "3.14" | ||
| runs: | ||
| using: composite | ||
| steps: | ||
| - name: Install Nixie and Merman CLI | ||
| shell: bash | ||
| env: | ||
| NIXIE_VERSION: ${{ inputs.nixie-version }} | ||
| MERMAN_VERSION: ${{ inputs.merman-version }} | ||
| PYTHON_VERSION: ${{ inputs.python-version }} | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| if ! command -v cargo >/dev/null 2>&1; then | ||
| echo "::error::cargo is required to install merman-cli" >&2 | ||
| exit 1 | ||
| fi | ||
| if ! command -v uv >/dev/null 2>&1; then | ||
| echo "::error::uv is required to install nixie-cli" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| if cargo binstall --version >/dev/null 2>&1; then | ||
| cargo binstall --no-confirm --locked "merman-cli@${MERMAN_VERSION}" | ||
| else | ||
| echo "cargo-binstall unavailable; building merman-cli from crates.io" | ||
| cargo install --locked merman-cli --version "=${MERMAN_VERSION}" | ||
| fi | ||
|
|
||
| uv tool install --python "${PYTHON_VERSION}" "nixie-cli==${NIXIE_VERSION}" | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Export the Nixie executable directory to subsequent workflow steps. Append uv tool install --python "${PYTHON_VERSION}" "nixie-cli==${NIXIE_VERSION}"
+ printf '%s\n' "$(uv tool dir --bin)" >> "$GITHUB_PATH"🤖 Prompt for AI AgentsSource: Linked repositories |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,152 @@ | ||
| """Behavioural tests for the install-nixie composite action.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import os | ||
| import shutil | ||
| import subprocess | ||
| import typing as typ | ||
| from pathlib import Path | ||
|
|
||
| import pytest | ||
| import yaml | ||
|
|
||
| ACTION_PATH = Path(__file__).resolve().parents[1] / "action.yml" | ||
|
|
||
|
|
||
| def _load_action() -> dict[str, typ.Any]: | ||
| """Load the install-nixie action manifest.""" | ||
| return yaml.safe_load(ACTION_PATH.read_text(encoding="utf-8")) | ||
|
|
||
|
|
||
| def _install_script() -> str: | ||
| """Return the action's installation shell fragment.""" | ||
| steps = _load_action()["runs"]["steps"] | ||
| assert len(steps) == 1, "install-nixie should have one atomic install step" | ||
| run_script = steps[0].get("run") | ||
| assert isinstance(run_script, str), "install step must define a shell script" | ||
| return run_script | ||
|
|
||
|
|
||
| def _write_executable(path: Path, content: str) -> None: | ||
| """Write an executable command stub.""" | ||
| path.write_text(content, encoding="utf-8") | ||
| path.chmod(0o755) | ||
|
|
||
|
|
||
| def _run_install_script( | ||
| tmp_path: Path, | ||
| *, | ||
| binstall_available: bool, | ||
| include_cargo: bool = True, | ||
| include_uv: bool = True, | ||
| ) -> subprocess.CompletedProcess[str]: | ||
| """Execute the install fragment against deterministic command stubs.""" | ||
| bash = shutil.which("bash") | ||
| if bash is None: | ||
| pytest.skip("bash not found on PATH") | ||
|
|
||
| stubs_dir = tmp_path / "stubs" | ||
| stubs_dir.mkdir() | ||
| calls_path = tmp_path / "calls" | ||
| if include_cargo: | ||
| binstall_status = 0 if binstall_available else 1 | ||
| _write_executable( | ||
| stubs_dir / "cargo", | ||
| f"""#!/bin/bash | ||
| set -euo pipefail | ||
| if [ "${{1:-}}" = "binstall" ] && [ "${{2:-}}" = "--version" ]; then | ||
| exit {binstall_status} | ||
| fi | ||
| printf 'cargo' >> "$CALLS_PATH" | ||
| printf ' <%s>' "$@" >> "$CALLS_PATH" | ||
| printf '\n' >> "$CALLS_PATH" | ||
| """, | ||
| ) | ||
| if include_uv: | ||
| _write_executable( | ||
| stubs_dir / "uv", | ||
| """#!/bin/bash | ||
| set -euo pipefail | ||
| printf 'uv' >> "$CALLS_PATH" | ||
| printf ' <%s>' "$@" >> "$CALLS_PATH" | ||
| printf '\n' >> "$CALLS_PATH" | ||
| """, | ||
| ) | ||
|
|
||
| env = { | ||
| **os.environ, | ||
| "CALLS_PATH": calls_path.as_posix(), | ||
| "MERMAN_VERSION": "0.7.0", | ||
| "NIXIE_VERSION": "1.1.0", | ||
| "PATH": stubs_dir.as_posix(), | ||
| "PYTHON_VERSION": "3.14", | ||
| } | ||
|
Comment on lines
+77
to
+84
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Test overridden input pins end-to-end. Parameterize the injected Nixie, Merman, and Python versions, then assert that non-default values reach both installer commands. Current tests only exercise defaults, so configurable input propagation is unprotected. As per coding guidelines, “Contract tests must validate that declared action inputs and outputs round-trip correctly.” 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| return subprocess.run( # noqa: S603,TID251 - exercise the bash fragment. | ||
| [bash, "-c", _install_script()], | ||
| cwd=tmp_path, | ||
| env=env, | ||
| capture_output=True, | ||
| text=True, | ||
| timeout=30, | ||
| ) | ||
|
Comment on lines
+85
to
+92
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Set Preserve the intentional non-zero return-code assertions while satisfying Ruff’s 🧰 Tools🪛 Ruff (0.15.21)[warning] 85-85: Add explicit (PLW1510) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
|
|
||
| def test_manifest_exposes_pinned_version_inputs() -> None: | ||
| """The action should expose the reviewed Nixie toolchain pins.""" | ||
| manifest = _load_action() | ||
|
|
||
| assert manifest["runs"]["using"] == "composite" | ||
| assert manifest["inputs"]["nixie-version"]["default"] == "1.1.0" | ||
| assert manifest["inputs"]["merman-version"]["default"] == "0.7.0" | ||
| assert manifest["inputs"]["python-version"]["default"] == "3.14" | ||
|
|
||
|
|
||
| def test_install_script_prefers_cargo_binstall(tmp_path: Path) -> None: | ||
| """Merman should use a locked binary install when cargo-binstall exists.""" | ||
| result = _run_install_script(tmp_path, binstall_available=True) | ||
|
|
||
| assert result.returncode == 0, result.stderr | ||
| assert (tmp_path / "calls").read_text(encoding="utf-8").splitlines() == [ | ||
| "cargo <binstall> <--no-confirm> <--locked> <merman-cli@0.7.0>", | ||
| "uv <tool> <install> <--python> <3.14> <nixie-cli==1.1.0>", | ||
| ] | ||
|
|
||
|
|
||
| def test_install_script_falls_back_to_cargo_install(tmp_path: Path) -> None: | ||
| """Merman should use a locked source build without cargo-binstall.""" | ||
| result = _run_install_script(tmp_path, binstall_available=False) | ||
|
|
||
| assert result.returncode == 0, result.stderr | ||
| assert "cargo-binstall unavailable" in result.stdout | ||
| assert (tmp_path / "calls").read_text(encoding="utf-8").splitlines() == [ | ||
| "cargo <install> <--locked> <merman-cli> <--version> <=0.7.0>", | ||
| "uv <tool> <install> <--python> <3.14> <nixie-cli==1.1.0>", | ||
| ] | ||
|
|
||
|
|
||
| @pytest.mark.parametrize( | ||
| ("include_cargo", "include_uv", "expected_error"), | ||
| [ | ||
| (False, True, "cargo is required to install merman-cli"), | ||
| (True, False, "uv is required to install nixie-cli"), | ||
| ], | ||
| ids=["missing-cargo", "missing-uv"], | ||
| ) | ||
| def test_install_script_reports_missing_prerequisite( | ||
| tmp_path: Path, | ||
| *, | ||
| include_cargo: bool, | ||
| include_uv: bool, | ||
| expected_error: str, | ||
| ) -> None: | ||
| """Missing runner prerequisites should produce actionable errors.""" | ||
| result = _run_install_script( | ||
| tmp_path, | ||
| binstall_available=False, | ||
| include_cargo=include_cargo, | ||
| include_uv=include_uv, | ||
| ) | ||
|
|
||
| assert result.returncode == 1 | ||
| assert expected_error in result.stderr | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Add the required repository-local usage example.
Add
uses: ./.github/actions/install-nixie@v1alongside the published-action example.Triage:
[type:docstyle]As per coding guidelines, “Each action README must include … a usage example using
uses: ./.github/actions/<name>@<major>.”🤖 Prompt for AI Agents
Sources: Coding guidelines, Path instructions