commit 14d7cae8640a31f59d54328430c4a33347c9c78b Author: Hugo Nijhuis Date: Thu Dec 25 23:16:20 2025 +0100 Add reusable Forgejo Actions Composite actions for CI/CD workflows: - checkout: Clone repo with configurable depth - docker-build: Build Docker images with multiple tags - docker-push: Push to Forgejo Packages registry - create-tag: Auto-increment semantic version tags - create-issue: Create issues via Forgejo API - create-pr: Create pull requests via Forgejo API - comment-pr: Add comments to PRs via Forgejo API All actions use shell scripts (no Node.js required). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 diff --git a/checkout/action.yaml b/checkout/action.yaml new file mode 100644 index 0000000..63729ff --- /dev/null +++ b/checkout/action.yaml @@ -0,0 +1,28 @@ +name: Checkout +description: Clone repository with configurable depth + +inputs: + repository: + description: Repository to clone (owner/repo) + default: ${{ github.repository }} + ref: + description: Branch, tag, or SHA to checkout + default: ${{ github.ref_name }} + depth: + description: Clone depth (0 for full history) + default: '1' + token: + description: Token for private repositories + default: ${{ github.token }} + +runs: + using: composite + steps: + - shell: bash + run: | + if [ "${{ inputs.depth }}" = "0" ]; then + git clone "https://oauth2:${{ inputs.token }}@code.flowmade.one/${{ inputs.repository }}.git" . + else + git clone --depth "${{ inputs.depth }}" --branch "${{ inputs.ref }}" \ + "https://oauth2:${{ inputs.token }}@code.flowmade.one/${{ inputs.repository }}.git" . + fi diff --git a/comment-pr/action.yaml b/comment-pr/action.yaml new file mode 100644 index 0000000..d6fb442 --- /dev/null +++ b/comment-pr/action.yaml @@ -0,0 +1,43 @@ +name: Comment on PR +description: Add a comment to a pull request via Forgejo API + +inputs: + token: + description: Forgejo API token + required: true + repository: + description: Repository (owner/repo) + default: ${{ github.repository }} + pr-number: + description: Pull request number + required: true + body: + description: Comment body + required: true + +outputs: + id: + description: Created comment ID + value: ${{ steps.comment.outputs.id }} + +runs: + using: composite + steps: + - id: comment + shell: bash + run: | + TOKEN="${{ inputs.token }}" + REPO="${{ inputs.repository }}" + PR_NUMBER="${{ inputs.pr-number }}" + BODY="${{ inputs.body }}" + + JSON=$(jq -n --arg body "$BODY" '{body: $body}') + + RESPONSE=$(curl -s -X POST \ + "https://code.flowmade.one/api/v1/repos/$REPO/issues/$PR_NUMBER/comments" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$JSON") + + ID=$(echo "$RESPONSE" | jq -r '.id') + echo "id=$ID" >> $GITHUB_OUTPUT diff --git a/create-issue/action.yaml b/create-issue/action.yaml new file mode 100644 index 0000000..f44166f --- /dev/null +++ b/create-issue/action.yaml @@ -0,0 +1,73 @@ +name: Create Issue +description: Create an issue via Forgejo API + +inputs: + token: + description: Forgejo API token + required: true + repository: + description: Repository (owner/repo) + default: ${{ github.repository }} + title: + description: Issue title + required: true + body: + description: Issue body + default: '' + labels: + description: Comma-separated label names + default: '' + assignees: + description: Comma-separated usernames to assign + default: '' + +outputs: + number: + description: Created issue number + value: ${{ steps.create.outputs.number }} + url: + description: Issue URL + value: ${{ steps.create.outputs.url }} + +runs: + using: composite + steps: + - id: create + shell: bash + run: | + TOKEN="${{ inputs.token }}" + REPO="${{ inputs.repository }}" + TITLE="${{ inputs.title }}" + BODY="${{ inputs.body }}" + LABELS="${{ inputs.labels }}" + ASSIGNEES="${{ inputs.assignees }}" + + # Build JSON payload + JSON=$(jq -n \ + --arg title "$TITLE" \ + --arg body "$BODY" \ + '{title: $title, body: $body}') + + # Add labels if provided + if [ -n "$LABELS" ]; then + LABELS_JSON=$(echo "$LABELS" | tr ',' '\n' | jq -R . | jq -s .) + JSON=$(echo "$JSON" | jq --argjson labels "$LABELS_JSON" '. + {labels: $labels}') + fi + + # Add assignees if provided + if [ -n "$ASSIGNEES" ]; then + ASSIGNEES_JSON=$(echo "$ASSIGNEES" | tr ',' '\n' | jq -R . | jq -s .) + JSON=$(echo "$JSON" | jq --argjson assignees "$ASSIGNEES_JSON" '. + {assignees: $assignees}') + fi + + RESPONSE=$(curl -s -X POST \ + "https://code.flowmade.one/api/v1/repos/$REPO/issues" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$JSON") + + NUMBER=$(echo "$RESPONSE" | jq -r '.number') + URL=$(echo "$RESPONSE" | jq -r '.html_url') + + echo "number=$NUMBER" >> $GITHUB_OUTPUT + echo "url=$URL" >> $GITHUB_OUTPUT diff --git a/create-pr/action.yaml b/create-pr/action.yaml new file mode 100644 index 0000000..497ed91 --- /dev/null +++ b/create-pr/action.yaml @@ -0,0 +1,83 @@ +name: Create Pull Request +description: Create a pull request via Forgejo API + +inputs: + token: + description: Forgejo API token + required: true + repository: + description: Repository (owner/repo) + default: ${{ github.repository }} + title: + description: Pull request title + required: true + body: + description: Pull request body + default: '' + head: + description: Source branch + required: true + base: + description: Target branch + default: 'main' + labels: + description: Comma-separated label names + default: '' + assignees: + description: Comma-separated usernames to assign + default: '' + +outputs: + number: + description: Created PR number + value: ${{ steps.create.outputs.number }} + url: + description: PR URL + value: ${{ steps.create.outputs.url }} + +runs: + using: composite + steps: + - id: create + shell: bash + run: | + TOKEN="${{ inputs.token }}" + REPO="${{ inputs.repository }}" + TITLE="${{ inputs.title }}" + BODY="${{ inputs.body }}" + HEAD="${{ inputs.head }}" + BASE="${{ inputs.base }}" + LABELS="${{ inputs.labels }}" + ASSIGNEES="${{ inputs.assignees }}" + + # Build JSON payload + JSON=$(jq -n \ + --arg title "$TITLE" \ + --arg body "$BODY" \ + --arg head "$HEAD" \ + --arg base "$BASE" \ + '{title: $title, body: $body, head: $head, base: $base}') + + # Add labels if provided + if [ -n "$LABELS" ]; then + LABELS_JSON=$(echo "$LABELS" | tr ',' '\n' | jq -R . | jq -s .) + JSON=$(echo "$JSON" | jq --argjson labels "$LABELS_JSON" '. + {labels: $labels}') + fi + + # Add assignees if provided + if [ -n "$ASSIGNEES" ]; then + ASSIGNEES_JSON=$(echo "$ASSIGNEES" | tr ',' '\n' | jq -R . | jq -s .) + JSON=$(echo "$JSON" | jq --argjson assignees "$ASSIGNEES_JSON" '. + {assignees: $assignees}') + fi + + RESPONSE=$(curl -s -X POST \ + "https://code.flowmade.one/api/v1/repos/$REPO/pulls" \ + -H "Authorization: token $TOKEN" \ + -H "Content-Type: application/json" \ + -d "$JSON") + + NUMBER=$(echo "$RESPONSE" | jq -r '.number') + URL=$(echo "$RESPONSE" | jq -r '.html_url') + + echo "number=$NUMBER" >> $GITHUB_OUTPUT + echo "url=$URL" >> $GITHUB_OUTPUT diff --git a/create-tag/action.yaml b/create-tag/action.yaml new file mode 100644 index 0000000..ddcc333 --- /dev/null +++ b/create-tag/action.yaml @@ -0,0 +1,49 @@ +name: Create Tag +description: Auto-increment and create a version tag + +inputs: + prefix: + description: Tag prefix (e.g., 'v', 'myapp-v') + default: 'v' + token: + description: Token for pushing tags + default: ${{ github.token }} + +outputs: + tag: + description: The created tag name + value: ${{ steps.version.outputs.tag }} + version: + description: The version number without prefix + value: ${{ steps.version.outputs.version }} + +runs: + using: composite + steps: + - id: version + shell: bash + run: | + PREFIX="${{ inputs.prefix }}" + + # Find latest tag with this prefix + LATEST_TAG=$(git tag -l "${PREFIX}*" | sort -V | tail -1) + + if [ -z "$LATEST_TAG" ]; then + NEXT_VERSION="${PREFIX}1.0.0" + else + # Extract version and increment patch + CURRENT_VERSION=${LATEST_TAG#$PREFIX} + IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION" + NEXT_VERSION="${PREFIX}${MAJOR}.${MINOR}.$((PATCH + 1))" + fi + + echo "tag=$NEXT_VERSION" >> $GITHUB_OUTPUT + echo "version=${NEXT_VERSION#$PREFIX}" >> $GITHUB_OUTPUT + + # Configure git and create tag + git config user.name forgejo-actions + git config user.email forgejo-actions@code.flowmade.one + git tag "$NEXT_VERSION" + + # Push tag using token + git push "https://oauth2:${{ inputs.token }}@code.flowmade.one/${{ github.repository }}.git" "$NEXT_VERSION" diff --git a/docker-build/action.yaml b/docker-build/action.yaml new file mode 100644 index 0000000..9d4940e --- /dev/null +++ b/docker-build/action.yaml @@ -0,0 +1,62 @@ +name: Docker Build +description: Build a Docker image with tags + +inputs: + image: + description: Full image name (registry/org/name) + required: true + tags: + description: Comma-separated list of tags + default: 'latest' + context: + description: Build context path + default: '.' + dockerfile: + description: Path to Dockerfile + default: 'Dockerfile' + build-args: + description: Build arguments (KEY=value, newline-separated) + default: '' + +outputs: + images: + description: Built image references + value: ${{ steps.build.outputs.images }} + +runs: + using: composite + steps: + - id: build + shell: bash + run: | + IMAGE="${{ inputs.image }}" + TAGS="${{ inputs.tags }}" + CONTEXT="${{ inputs.context }}" + DOCKERFILE="${{ inputs.dockerfile }}" + BUILD_ARGS="${{ inputs.build-args }}" + + # Build tag arguments + TAG_ARGS="" + IMAGES="" + IFS=',' read -ra TAG_ARRAY <<< "$TAGS" + for tag in "${TAG_ARRAY[@]}"; do + tag=$(echo "$tag" | xargs) # trim whitespace + TAG_ARGS="$TAG_ARGS -t $IMAGE:$tag" + IMAGES="$IMAGES$IMAGE:$tag," + done + IMAGES="${IMAGES%,}" # remove trailing comma + + # Build build-arg arguments + BUILD_ARG_ARGS="" + if [ -n "$BUILD_ARGS" ]; then + while IFS= read -r arg; do + if [ -n "$arg" ]; then + BUILD_ARG_ARGS="$BUILD_ARG_ARGS --build-arg $arg" + fi + done <<< "$BUILD_ARGS" + fi + + # Build the image + docker build $TAG_ARGS $BUILD_ARG_ARGS -f "$DOCKERFILE" "$CONTEXT" + + echo "images=$IMAGES" >> $GITHUB_OUTPUT diff --git a/docker-push/action.yaml b/docker-push/action.yaml new file mode 100644 index 0000000..51bd8d6 --- /dev/null +++ b/docker-push/action.yaml @@ -0,0 +1,41 @@ +name: Docker Push +description: Push Docker image to Forgejo Packages registry + +inputs: + image: + description: Full image name (registry/org/name) + required: true + tags: + description: Comma-separated list of tags to push + default: 'latest' + registry: + description: Registry URL + default: 'code.flowmade.one' + username: + description: Registry username + default: ${{ github.repository_owner }} + password: + description: Registry password/token + required: true + +runs: + using: composite + steps: + - shell: bash + run: | + IMAGE="${{ inputs.image }}" + TAGS="${{ inputs.tags }}" + REGISTRY="${{ inputs.registry }}" + USERNAME="${{ inputs.username }}" + PASSWORD="${{ inputs.password }}" + + # Login to registry + echo "$PASSWORD" | docker login "$REGISTRY" -u "$USERNAME" --password-stdin + + # Push each tag + IFS=',' read -ra TAG_ARRAY <<< "$TAGS" + for tag in "${TAG_ARRAY[@]}"; do + tag=$(echo "$tag" | xargs) # trim whitespace + echo "Pushing $IMAGE:$tag" + docker push "$IMAGE:$tag" + done