Skip to content
Merged
Show file tree
Hide file tree
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
101 changes: 101 additions & 0 deletions .github/actions/ci-setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
name: ci-setup
description: "Composite action to install Erlang/rebar3 and Gleam (pinned) and restore Gleam cache"
inputs:
otp-version:
description: 'OTP version'
required: false
default: '28.1'
rebar3-version:
description: 'rebar3 version'
required: false
default: '3.25.1'
gleam-version:
description: 'Gleam version'
required: false
default: '1.13.0'
arch:
description: 'Architecture for Gleam asset'
required: false
default: 'linux-x86_64'

runs:
using: "composite"
steps:
- name: Restore Gleam cache
uses: actions/cache@v4
with:
path: ~/.cache/gleam
key: ${{ runner.os }}-gleam-${{ hashFiles('**/gleam.toml') }}

- name: Install Erlang/OTP, rebar3 and (optionally) Gleam
uses: erlef/setup-beam@v1
with:
otp-version: ${{ inputs.otp-version }}
rebar3-version: ${{ inputs.rebar3-version }}
gleam-version: ${{ inputs.gleam-version }}

- name: Ensure Gleam is installed (fallbacks)
env:
GLEAM_VERSION: ${{ inputs.gleam-version }}
ARCH: ${{ inputs.arch }}
shell: bash
run: |
set -eux
# If erlef/setup-beam already provided gleam, we're done
if command -v gleam >/dev/null 2>&1; then
echo "gleam already installed"
gleam --version || true
exit 0
fi
TAG="v${GLEAM_VERSION}"
API_URL="https://api.github.com/repos/gleam-lang/gleam/releases/tags/${TAG}"
echo "Querying ${API_URL} for assets"

# Try exact-match first (version+arch)
DOWNLOAD_URL=$(curl -fsSL "${API_URL}" \
| grep -oE '"browser_download_url":\s*"[^"]+' \
| sed -E 's/.*"([^\"]+)$/\1/' \
| grep "${GLEAM_VERSION}-${ARCH}" || true)

# If not found, try any linux/x86_64-ish asset
if [ -z "${DOWNLOAD_URL}" ]; then
echo "Exact asset not found; searching for any linux/x86_64 asset"
DOWNLOAD_URL=$(curl -fsSL "${API_URL}" \
| grep -oE '"browser_download_url":\s*"[^"]+' \
| sed -E 's/.*"([^\"]+)$/\1/' \
| grep -Ei "linux|x86|x86_64|amd64" | head -n1 || true)
fi

if [ -n "${DOWNLOAD_URL}" ]; then
echo "Found asset: ${DOWNLOAD_URL}"
curl -fsSL -o /tmp/gleam.tar.gz "${DOWNLOAD_URL}"
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 Gleam binary is downloaded via curl without verifying checksums or signatures. This could pose a security risk if the download is compromised or redirected. Consider verifying the downloaded artifact against published checksums or signatures from the Gleam releases page.

Suggested change
curl -fsSL -o /tmp/gleam.tar.gz "${DOWNLOAD_URL}"
curl -fsSL -o /tmp/gleam.tar.gz "${DOWNLOAD_URL}"
# Download and verify checksum
CHECKSUM_URL="${DOWNLOAD_URL}.sha256"
echo "Downloading checksum from ${CHECKSUM_URL}"
curl -fsSL -o /tmp/gleam.tar.gz.sha256 "${CHECKSUM_URL}"
EXPECTED_SUM=$(cat /tmp/gleam.tar.gz.sha256 | awk '{print $1}')
ACTUAL_SUM=$(sha256sum /tmp/gleam.tar.gz | awk '{print $1}')
if [ "$EXPECTED_SUM" != "$ACTUAL_SUM" ]; then
echo "Checksum verification failed!"
echo "Expected: $EXPECTED_SUM"
echo "Actual: $ACTUAL_SUM"
exit 1
fi
echo "Checksum verified."

Copilot uses AI. Check for mistakes.
tar -xzf /tmp/gleam.tar.gz -C /usr/local/bin --strip-components=1
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 tar command attempts to extract to /usr/local/bin which typically requires root permissions. On GitHub Actions runners, this will likely fail with a permission denied error. Consider using sudo tar or extracting to a user-writable location like $HOME/.local/bin or $HOME/.cargo/bin and ensuring it's in the PATH.

Suggested change
tar -xzf /tmp/gleam.tar.gz -C /usr/local/bin --strip-components=1
mkdir -p "$HOME/.local/bin"
tar -xzf /tmp/gleam.tar.gz -C "$HOME/.local/bin" --strip-components=1
export PATH="$HOME/.local/bin:$PATH"

Copilot uses AI. Check for mistakes.
gleam --version || true
exit 0
fi

# No prebuilt asset found — fallback to building/installing via cargo
echo "No prebuilt Gleam asset found for ${TAG}; falling back to cargo install (build from source)"

# Install rustup if necessary
if ! command -v cargo >/dev/null 2>&1; then
echo "Installing rustup/cargo"
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
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.

[nitpick] The rustup installation script is piped directly to sh without verification. While this is the official installation method, it poses a security risk if the source is compromised. Consider adding a checksum verification or using a pinned version of the installer script.

Suggested change
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
# Download rustup installer script
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs -o /tmp/rustup-init.sh
# Verify SHA256 hash (update this hash if the installer script changes)
EXPECTED_HASH="REPLACE_WITH_KNOWN_GOOD_HASH"
ACTUAL_HASH="$(sha256sum /tmp/rustup-init.sh | awk '{print $1}')"
if [ "$ACTUAL_HASH" != "$EXPECTED_HASH" ]; then
echo "rustup-init.sh hash mismatch! Aborting."
exit 1
fi
sh /tmp/rustup-init.sh -y

Copilot uses AI. Check for mistakes.
export PATH="$HOME/.cargo/bin:$PATH"
fi

# Ensure latest stable toolchain
rustup default stable || true

# Install gleam from git tag (faster and avoids building all optional targets)
cargo install --locked --git https://github.com/gleam-lang/gleam --tag "${TAG}" || {
echo "cargo install failed; attempting cargo build from source"
tmpdir=$(mktemp -d)
git clone --depth 1 --branch "${TAG}" https://github.com/gleam-lang/gleam.git "$tmpdir"
cd "$tmpdir"
cargo build --release
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 fallback build copies the binary to $HOME/.cargo/bin/ but doesn't ensure this directory exists first. If rustup was just installed, the directory should exist, but if cargo was already present, this path might not exist. Consider adding mkdir -p "$HOME/.cargo/bin" before the copy operation to ensure it succeeds.

Suggested change
cargo build --release
cargo build --release
mkdir -p "$HOME/.cargo/bin"

Copilot uses AI. Check for mistakes.
cp target/release/gleam "$HOME/.cargo/bin/"
}

export PATH="$HOME/.cargo/bin:$PATH"
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 PATH is exported within the script but these exports only affect the current shell session. In GitHub Actions composite actions, PATH modifications need to be persisted to subsequent steps using echo "$HOME/.cargo/bin" >> $GITHUB_PATH. Without this, the Gleam binary installed via cargo will not be accessible in the parent workflow's subsequent steps.

Suggested change
export PATH="$HOME/.cargo/bin:$PATH"
export PATH="$HOME/.cargo/bin:$PATH"
echo "$HOME/.cargo/bin" >> $GITHUB_PATH

Copilot uses AI. Check for mistakes.
gleam --version
68 changes: 68 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
name: CI

on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]

jobs:
run-tests:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

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

- name: Cache Gleam deps and build
uses: actions/cache@v4
with:
path: |
~/.cache/gleam
~/.gleam
./_gleam_deps
./build
key: ${{ runner.os }}-gleam-1.13.0-otp28-cache-${{ hashFiles('**/gleam.toml') }}
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 cache key hardcodes gleam-1.13.0-otp28 but the actual versions are specified in the setup step inputs (lines 19-21). If those input values change, the cache key will become stale and won't match. Consider using ${{ inputs.gleam-version }} or deriving the key from the setup inputs to ensure consistency.

Copilot uses AI. Check for mistakes.
restore-keys: |
${{ runner.os }}-gleam-1.13.0-otp28-cache-
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 cache restore-keys hardcode gleam-1.13.0-otp28 but the actual versions are specified in the setup step inputs. If those input values change, the restore keys will become stale. Consider using the actual version values from the setup inputs for consistency.

Copilot uses AI. Check for mistakes.

Comment on lines +23 to +34
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 cache paths include both ~/.cache/gleam (line 27 in ci-setup action) and ~/.cache/gleam (line 27 in workflow), creating duplicate caching. Additionally, the cache in the workflow includes more paths and has version/OTP-specific keys, while the action's cache has a simpler key. This could lead to cache conflicts or inefficiency. Consider consolidating the caching strategy to either the workflow or the action, not both.

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

Copilot uses AI. Check for mistakes.
- name: Install dependencies
run: |
set -eux
gleam deps download

- name: Check formatting
run: |
set -eux
gleam format --check src test

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

- name: Run all tests
run: |
set -eux
gleam test

- name: Prepare CI artifacts
if: always()
run: |
set -eux
mkdir -p ci_artifacts
echo "tests: ok" > ci_artifacts/test-success.txt
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 step always creates test-success.txt with "tests: ok" even if the tests actually failed. Since if: always() is used, this file will be created and uploaded regardless of test outcome, which could be misleading. Consider only creating this file when tests succeed, or include the actual test status in the artifact.

Copilot uses AI. Check for mistakes.
if [ -d build ]; then cp -R build ci_artifacts/ || true; fi

- name: Upload CI artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: ci-artifacts
path: ci_artifacts