Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
879a5db
fix(actions): pass untrusted context through env in composite actions
MAfarrag Jul 20, 2026
8cef44a
chore(release/github): pin action-gh-release to a SHA and set actor v…
MAfarrag Jul 20, 2026
f304da6
ci: least-privilege token scope and pin setup-uv in test workflows
MAfarrag Jul 20, 2026
7f04c4e
fix(mkdocs-deploy): fail fast when a release trigger has no resolvabl…
MAfarrag Jul 20, 2026
9e83361
test(mkdocs-deploy): assert tag presence flips release success/failure
MAfarrag Jul 21, 2026
a70a7e4
ci(deploy-docs): drop unused pages: write permission
MAfarrag Jul 21, 2026
1ac327e
ci(test-release-github): note contents: read relies on skip-github-re…
MAfarrag Jul 21, 2026
510fc7f
style(mkdocs-deploy): add trailing newline at end of file
MAfarrag Jul 21, 2026
549d272
fix(release/github): env-scope caller inputs in run blocks to close i…
MAfarrag Jul 21, 2026
8b25004
fix(release/github): env-scope step outputs re-interpolated into run …
MAfarrag Jul 21, 2026
424bea6
feat(release/github)!: make install-groups required and drop the default
MAfarrag Jul 21, 2026
a62b7c6
Merge branch 'main' of github.com:Serapieum-of-alex/github-actions in…
MAfarrag Jul 21, 2026
b568b51
feat(release/github): fail fast with a clear error when commitizen is…
MAfarrag Jul 21, 2026
544862f
docs(release/github): reflect required install-groups (no default) in…
MAfarrag Jul 21, 2026
1ae17fd
fix(release/github): run the commitizen preflight from WORK_DIR and s…
MAfarrag Jul 21, 2026
384bed1
docs(release/github): add required install-groups to all examples
MAfarrag Jul 21, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ on:

permissions:
contents: write
pages: write

jobs:
docs:
Expand All @@ -34,9 +33,11 @@ jobs:
run: python scripts/generate_action_docs.py

- name: Configure Git
env:
GIT_ACTOR: ${{ github.actor }}
run: |
git config --global user.name '${{ github.actor }}'
git config --global user.email '${{ github.actor }}@users.noreply.github.com'
git config --global user.name "$GIT_ACTOR"
git config --global user.email "${GIT_ACTOR}@users.noreply.github.com"

- name: Deploy with mike
run: |
Expand Down
34 changes: 29 additions & 5 deletions .github/workflows/test-mkdocs-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ on:
push:

permissions:
contents: write
pages: write
contents: read

jobs:
test-pull-request-trigger:
Expand Down Expand Up @@ -361,23 +360,48 @@ jobs:
cp tests/data/mkdocs-deploy/test-release-tag-resolution/uv.lock .
cp -r tests/data/mkdocs-deploy/test-release-tag-resolution/docs .

# Positive control: a release trigger WITH a resolvable tag must succeed.
- name: Test with explicit release tag
id: with-tag
continue-on-error: true
uses: ./actions/mkdocs-deploy
with:
trigger: 'release'
package-manager: 'uv'
release-tag: 'v4.5.6'
deploy-token: ${{ secrets.GITHUB_TOKEN }}

- name: Test without explicit release tag (simulated)
env:
GITHUB_EVENT_NAME: release
# Negative test: the SAME fixture and setup but with NO resolvable tag (no
# release-tag input, and github.event.release.tag_name is empty because this
# workflow is push-triggered). The action must fail fast in its tag-resolution
# guard rather than deploy a bogus version.
- name: Test release trigger without a resolvable tag fails fast
id: no-tag
continue-on-error: true
uses: ./actions/mkdocs-deploy
with:
trigger: 'release'
package-manager: 'uv'
deploy-token: ${{ secrets.GITHUB_TOKEN }}

# Assert that the ONLY difference (presence of a resolvable tag) is what flips
# the result: tagged succeeds, untagged fails. Because both runs share the same
# fixture and setup, an unrelated regression that broke setup would also fail
# the positive control and be caught here instead of passing silently.
- name: Verify tag presence flips success/failure
run: |
echo "with-tag outcome: ${{ steps.with-tag.outcome }}"
echo "no-tag outcome: ${{ steps.no-tag.outcome }}"
if [ "${{ steps.with-tag.outcome }}" != "success" ]; then
echo "::error::Positive control failed: a release deploy WITH a tag should succeed"
exit 1
fi
if [ "${{ steps.no-tag.outcome }}" != "failure" ]; then
echo "::error::A release deploy WITHOUT a resolvable tag should fail fast in the guard"
exit 1
fi
echo "[OK] Tag presence flips success/failure as expected"

test-default-install-groups:
name: Test Default install-groups Value
runs-on: ubuntu-latest
Expand Down
11 changes: 10 additions & 1 deletion .github/workflows/test-release-github.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ on:
branches: [main]
workflow_dispatch:

# contents: read is sufficient ONLY because every job below invokes
# ./actions/release/github with skip-github-release: 'true' (the GitHub Releases
# API is never called) and all pushes target the local fake remote. A job that
# creates a real GitHub release would need contents: write.
permissions:
contents: write
contents: read

jobs:
# Test basic functionality with different package managers
Expand Down Expand Up @@ -264,6 +268,7 @@ jobs:
uses: ./actions/release/github
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
install-groups: 'groups: docs dev'
increment: 'patch'
prerelease-type: ${{ matrix.prerelease-type }}
package-manager: 'uv'
Expand Down Expand Up @@ -458,6 +463,7 @@ jobs:
uses: ./actions/release/github
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
install-groups: 'groups: docs dev'
increment: 'patch'
package-manager: 'uv'
python-version: ${{ matrix.python-version }}
Expand Down Expand Up @@ -534,6 +540,7 @@ jobs:
uses: ./actions/release/github
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
install-groups: 'groups: docs dev'
increment: 'patch'
package-manager: 'uv'
python-version: '3.12'
Expand Down Expand Up @@ -614,6 +621,7 @@ jobs:
uses: ./actions/release/github
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
install-groups: 'groups: docs dev'
increment: 'patch'
package-manager: 'uv'
python-version: '3.12'
Expand Down Expand Up @@ -756,6 +764,7 @@ jobs:
uses: ./actions/release/github
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
install-groups: 'groups: docs dev'
increment: 'patch'
package-manager: 'uv'
python-version: '3.12'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/test-release-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ jobs:

# Generate a fresh lock so verify-lock='true' always sees an up-to-date file.
- name: Generate fresh uv lock
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: false

Expand Down Expand Up @@ -419,7 +419,7 @@ jobs:
# Since we patched the version, the committed lock is now stale — regenerate.
- name: Regenerate uv lock after version patch (uv only)
if: matrix.package-manager == 'uv'
uses: astral-sh/setup-uv@v7
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
with:
enable-cache: false

Expand Down
37 changes: 21 additions & 16 deletions actions/mkdocs-deploy/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -189,23 +189,33 @@ runs:

- name: Configure Git
shell: bash
env:
GIT_ACTOR: ${{ github.actor }}
run: |
echo "::group::Configuring Git"
git config --global user.name '${{ github.actor }}'
git config --global user.email '${{ github.actor }}@users.noreply.github.com'
git config --global user.name "$GIT_ACTOR"
git config --global user.email "${GIT_ACTOR}@users.noreply.github.com"
echo "::endgroup::"

- name: Set release tag environment variable
if: ${{ inputs.trigger == 'release' }}
shell: bash
env:
INPUT_RELEASE_TAG: ${{ inputs.release-tag }}
EVENT_RELEASE_TAG: ${{ github.event.release.tag_name }}
run: |
echo "::group::Setting release tag"
if [ -n "${{ inputs.release-tag }}" ]; then
export RELEASE_TAG_VERSION="${{ inputs.release-tag }}"
if [ -n "$INPUT_RELEASE_TAG" ]; then
RELEASE_TAG_VERSION="$INPUT_RELEASE_TAG"
else
export RELEASE_TAG_VERSION="${{ github.event.release.tag_name }}"
RELEASE_TAG_VERSION="$EVENT_RELEASE_TAG"
fi
if [ -z "$RELEASE_TAG_VERSION" ]; then
echo "::error::trigger is 'release' but no release tag could be resolved."
echo "::error::Provide the 'release-tag' input, or run on a release event so github.event.release.tag_name is set."
exit 1
fi
echo "RELEASE_TAG_VERSION=${RELEASE_TAG_VERSION}" >> $GITHUB_ENV
echo "RELEASE_TAG_VERSION=${RELEASE_TAG_VERSION}" >> "$GITHUB_ENV"
echo "Release tag: ${RELEASE_TAG_VERSION}"
echo "::endgroup::"

Expand Down Expand Up @@ -243,16 +253,11 @@ runs:
MIKE_ALIAS: ${{ inputs.mike-alias }}
run: |
echo "::group::Deploying release documentation"
# A resolvable release tag is guaranteed here: the "Set release tag" step
# fails fast (exit 1) when neither release-tag nor github.event.release.tag_name
# is set, so this deploy always has a real version. mike_push.sh handles the
# package-manager dispatch and retries the push on a concurrent gh-pages update.
SCRIPT="${{ github.action_path }}/scripts/mike_push.sh"
if [ -n "$RELEASE_TAG_VERSION" ]; then
bash "$SCRIPT" deploy --push --update-aliases "$RELEASE_TAG_VERSION" "$MIKE_ALIAS"
else
# No resolvable release tag (e.g. a simulated/dispatch release): deploy
# the alias name as the version, keeping --update-aliases so mike may
# reassign a pre-existing alias of that name instead of erroring
# ("version 'latest' already exists"). Matches the prior unquoted form
# `mike deploy --push --update-aliases <alias>`.
bash "$SCRIPT" deploy --push --update-aliases "$MIKE_ALIAS"
fi
bash "$SCRIPT" deploy --push --update-aliases "$RELEASE_TAG_VERSION" "$MIKE_ALIAS"
bash "$SCRIPT" set-default --push "$MIKE_ALIAS"
echo "::endgroup::"
34 changes: 20 additions & 14 deletions actions/python-setup/pip/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,12 @@ runs:
- name: Validate cache requirements
if: inputs.cache != ''
shell: bash
env:
CACHE: ${{ inputs.cache }}
run: |
echo "::group::Validating cache requirements"
if [ ! -f pyproject.toml ] && [ ! -f requirements.txt ]; then
echo "::error::Cache is enabled (cache='${{ inputs.cache }}') but no pyproject.toml or requirements.txt found."
echo "::error::Cache is enabled (cache='${CACHE}') but no pyproject.toml or requirements.txt found."
echo "::error::Either create a pyproject.toml/requirements.txt file or set cache: '' to disable caching."
exit 1
fi
Expand All @@ -63,28 +65,32 @@ runs:

- name: Upgrade pip
shell: bash
env:
INSTALL_GROUPS: ${{ inputs.install-groups }}
run: |
echo "::group::Upgrading pip"
python -m pip install --upgrade pip
pip_version=$(pip --version | awk '{print $2}')
echo "pip upgraded to $pip_version"
if [[ "${{ inputs.install-groups }}" == *"groups:"* ]]; then
if [[ "$INSTALL_GROUPS" == *"groups:"* ]]; then
echo "Note: installing PEP 735 dependency groups requires pip >= 25.1."
fi
echo "::endgroup::"

- name: Install dependencies
shell: bash
env:
INSTALL_GROUPS: ${{ inputs.install-groups }}
run: |
echo "::group::Installing dependencies"
if [ -f pyproject.toml ]; then
echo "Found pyproject.toml, installing package..."

# Check if install-groups are specified
if [ -n "${{ inputs.install-groups }}" ]; then
echo "Processing install-groups: ${{ inputs.install-groups }}"
input_str="${{ inputs.install-groups }}"
if [ -n "$INSTALL_GROUPS" ]; then
echo "Processing install-groups: $INSTALL_GROUPS"

input_str="$INSTALL_GROUPS"
dependency_groups=()
optional_deps=()

Expand Down Expand Up @@ -155,9 +161,9 @@ runs:
extras_list="$extras_list,$extra"
fi
done
install_cmd="pip install .[$extras_list]"
echo "Installing package with optional dependencies: $install_cmd"
eval "$install_cmd"
install_target=".[$extras_list]"
echo "Installing package with optional dependencies: pip install $install_target"
pip install "$install_target"
else
echo "Installing package without optional dependencies..."
pip install .
Expand All @@ -166,9 +172,9 @@ runs:
# Install dependency groups if any (requires pip with PEP 735 support)
if [ ${#dependency_groups[@]} -gt 0 ]; then
# Build command with multiple --group flags
dep_groups_cmd="pip install ."
dep_groups_args=()
for group in "${dependency_groups[@]}"; do
dep_groups_cmd="$dep_groups_cmd --group $group"
dep_groups_args+=("--group" "$group")
done

# Dependency groups (pip install --group) are a PEP 735 feature
Expand All @@ -181,8 +187,8 @@ runs:
# unsupported branch is fatal.
group_help="$(pip install --help)"
if grep -q -- "--group" <<<"$group_help"; then
echo "Installing dependency groups: $dep_groups_cmd"
eval "$dep_groups_cmd"
echo "Installing dependency groups: pip install . ${dep_groups_args[*]}"
pip install . "${dep_groups_args[@]}"
else
groups_list=$(IFS=,; echo "${dependency_groups[*]}")
echo "::error::pip $pip_version does not support the --group flag (PEP 735 dependency groups)."
Expand Down
8 changes: 6 additions & 2 deletions actions/python-setup/pixi/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,20 @@ runs:

- name: List environment info
shell: bash
env:
ACTIVATE_ENV: ${{ inputs.activate-environment }}
run: |
echo "::group::Pixi environment information"
echo "Active environment: ${{ inputs.activate-environment }}"
echo "Active environment: $ACTIVATE_ENV"
echo "Available environments:"
pixi info
echo "::endgroup::"

- name: List installed packages
shell: bash
env:
ACTIVATE_ENV: ${{ inputs.activate-environment }}
run: |
echo "::group::Installed packages summary"
pixi list -e ${{ inputs.activate-environment }}
pixi list -e "$ACTIVATE_ENV"
echo "::endgroup::"
10 changes: 6 additions & 4 deletions actions/python-setup/uv/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ runs:

- name: Install Dependencies
shell: bash
env:
INSTALL_GROUPS: ${{ inputs.install-groups }}
run: |
echo "::group::Preparing uv sync command"

Expand All @@ -88,10 +90,10 @@ runs:
installed_extras=()

# Check if install-groups is provided
if [[ -n "${{ inputs.install-groups }}" ]]; then
echo "Processing install-groups: ${{ inputs.install-groups }}"
input_str="${{ inputs.install-groups }}"
if [[ -n "$INSTALL_GROUPS" ]]; then
echo "Processing install-groups: $INSTALL_GROUPS"

input_str="$INSTALL_GROUPS"

# Extract groups section if present
if [[ "$input_str" =~ groups:[[:space:]]*([^,]*)(,|$) ]]; then
Expand Down
Loading
Loading