Skip to content
Merged
Changes from all commits
Commits
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
169 changes: 169 additions & 0 deletions .github/workflows/publish-gleam.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
name: Publish (Gleam)

# Publish package using `gleam publish` when a tag matching v* is pushed.
# Steps:
# - checkout (full history)
# - setup OTP / Gleam
# - build & test
# - verify the tag version matches `gleam.toml`
# - verify HEX API key via HTTP
# - publish with `gleam publish --yes` (non-interactive)

on:
push:
tags:
- 'v*'
pull_request:
types: [closed]
branches: [main]

permissions:
contents: write
packages: write
id-token: write

jobs:
publish:
name: Release to Hex
runs-on: ubuntu-latest
if: >-
contains(github.ref, 'refs/tags/') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true)
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Setup runtime
uses: ./.github/actions/ci-setup
with:
otp-version: '28.1'
gleam-version: '1.13.0'

- name: Show versions
run: |
erl -version || true
gleam --version || true

- name: Cache Gleam deps
uses: actions/cache@v4
with:
path: |
~/.cache/gleam
~/.gleam
./_gleam_deps
./build
key: ${{ runner.os }}-gleam-1.13.0-otp28-publish-${{ hashFiles('**/gleam.toml') }}
restore-keys: |
${{ runner.os }}-gleam-1.13.0-otp28-publish-

- name: Install dependencies
run: |
set -eux
gleam deps download

- name: Build
run: |
set -eux
gleam build

- name: Run tests (sanity)
run: |
set -eux
gleam test

- name: Determine version from tag
id: ver
run: |
TAG=${GITHUB_REF#refs/tags/}
VERSION=${TAG#v}
echo "Determined version ${VERSION} from tag ${TAG}"
echo "version=${VERSION}" >> $GITHUB_OUTPUT
Comment on lines +75 to +81
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Determine version from tag" step will fail when the workflow is triggered by a merged PR, since GITHUB_REF won't contain a tag reference at this point (the tag is created later in line 83-99). This step should be conditional on tag push events only, or the logic needs to be restructured to handle both trigger types.

Copilot uses AI. Check for mistakes.

- name: Create tag from gleam.toml (on merged PR)
if: github.event_name == 'pull_request' && github.event.pull_request.merged == true
run: |
set -euo pipefail
VERSION=$(grep '^version' gleam.toml | sed -E 's/.*= *"([^\"]+)".*/\1/')
TAG="v${VERSION}"
echo "Derived tag: ${TAG}"
git fetch origin --tags
if git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null; then
echo "Tag ${TAG} already exists, skipping"
exit 0
Comment on lines +92 to +93
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a tag already exists (line 92), the step exits with code 0 but the workflow continues. This means the workflow will attempt to verify and publish using a version that may not match the current state. The job-level if condition (line 29-30) checks for tag refs, but after exiting at line 93, GITHUB_REF still won't contain a tag reference. Consider restructuring the logic to either fail the workflow or properly set the tag reference for subsequent steps.

Suggested change
echo "Tag ${TAG} already exists, skipping"
exit 0
echo "Tag ${TAG} already exists, aborting workflow."
exit 1

Copilot uses AI. Check for mistakes.
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag -a "${TAG}" -m "Release ${TAG}"
git push origin "${TAG}"
echo "Pushed tag ${TAG} to origin"

- name: Verify tag matches `gleam.toml`
run: |
VERSION="${{ steps.ver.outputs.version }}"
FILE_VER=$(grep '^version' gleam.toml | sed -E 's/.*= *"([^"]+)".*/\1/') || true
echo "gleam.toml version: ${FILE_VER}"
if [ -n "${FILE_VER}" ] && [ "${FILE_VER}" != "${VERSION}" ]; then
echo "Tag version (${VERSION}) does not match gleam.toml (${FILE_VER}). Aborting publish."
exit 1
fi
Comment on lines +101 to +109
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This verification step references steps.ver.outputs.version which is only set by the "Determine version from tag" step (line 75-81). When triggered by a merged PR, the version won't be extracted at that point since the tag doesn't exist yet. This step needs to either: (1) be conditional on tag push events only, or (2) extract the version from gleam.toml when triggered by PR merge.

Copilot uses AI. Check for mistakes.

- name: Verify HEX API key via HTTP
env:
HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
run: |
set -euo pipefail
if [ -z "${HEX_API_KEY:-}" ]; then
echo "HEX_API_KEY secret is not set; aborting"
exit 1
fi
# ask for JSON explicitly
RES=$(curl -s -w "%{http_code}" -H "Authorization: Bearer ${HEX_API_KEY}" -H "Accept: application/vnd.hex+json; version=1.0" https://hex.pm/api/me)
HTTP=${RES: -3}
BODY=${RES:: -3}
echo "HTTP status: ${HTTP}"
if [ "${HTTP}" != "200" ]; then
echo "HEX API key invalid or API returned ${HTTP}. Response body preview:"
echo "${BODY}" | sed -n '1,40p'
exit 1
fi

- name: Check if version already published on Hex
run: |
set -euo pipefail
PACKAGE=$(grep '^name' gleam.toml | sed -E 's/.*= *"([^"]+)".*/\1/')
VERSION="${{ steps.ver.outputs.version }}"
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step references steps.ver.outputs.version which may not be set when the workflow is triggered by a merged PR. The version extraction logic needs to handle both trigger types (tag push and merged PR) consistently.

Copilot uses AI. Check for mistakes.
echo "Checking Hex for package ${PACKAGE} version ${VERSION}"
if curl -s "https://hex.pm/api/packages/${PACKAGE}" | jq -e ".releases[] | select(.version==\"${VERSION}\")" > /dev/null; then
echo "Version ${VERSION} already exists on Hex. Skipping publish."
exit 0
Comment on lines +137 to +139
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exit 0 here doesn't prevent subsequent steps from running. If the version already exists on Hex, the workflow will continue to the "Publish to Hex" step which will likely fail. Consider using exit 1 with an appropriate error message, or set an output variable to conditionally skip the publish step.

Copilot uses AI. Check for mistakes.
fi

- name: Publish to Hex
if: startsWith(github.ref, 'refs/tags/') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true)
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The conditional logic here is checking startsWith(github.ref, 'refs/tags/'), but when triggered by a merged PR, the "Create tag from gleam.toml" step (line 83-99) may have skipped tag creation if the tag already existed. This means the publish step might execute even when no tag is present in the current context. The condition should be more robust to ensure a valid version context exists before publishing.

Copilot uses AI. Check for mistakes.
env:
HEX_API_KEY: ${{ secrets.HEX_API_KEY }}
run: |
set -euo pipefail
echo "Publishing with gleam publish --yes"
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The piped input "I am not using semantic versioning" appears to be handling an interactive prompt from gleam publish. This should be documented in a comment explaining why this input is needed, as it's not immediately clear what prompt this is responding to or if this is the correct response.

Suggested change
echo "Publishing with gleam publish --yes"
echo "Publishing with gleam publish --yes"
# gleam publish prompts for confirmation if the version is not semver; this input answers the prompt.
# See: https://github.com/gleam-lang/gleam/issues/2542

Copilot uses AI. Check for mistakes.
echo "I am not using semantic versioning" | gleam publish --yes

- name: Create GitHub Release
if: startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
body: |
Release ${{ github.ref_name }}

See [CHANGELOG.md](https://github.com/${{ github.repository }}/blob/${{ github.ref_name }}/CHANGELOG.md) for details.
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Upload package artifact (audit)
if: always()
uses: actions/upload-artifact@v4
with:
name: gleam-hex-package
path: _build/default/lib/thrifty/hex/thrifty-*.tar || true
Copy link

Copilot AI Nov 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The artifact path appears to be hardcoded with the package name "thrifty". If this workflow is intended to be reusable or the package name changes, this path should use the dynamically extracted package name from gleam.toml (similar to line 134) or use a wildcard pattern like _build/default/lib/*/hex/*.tar.

Suggested change
path: _build/default/lib/thrifty/hex/thrifty-*.tar || true
path: _build/default/lib/*/hex/*.tar

Copilot uses AI. Check for mistakes.