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
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
117 changes: 117 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
name: CI

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true

jobs:
build:
name: GHC ${{ matrix.ghc }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
ghc: ["9.6", "9.8", "9.10", "9.12", "9.14"]
include:
- os: macos-latest
ghc: "9.12"
- os: windows-latest
ghc: "9.12"
steps:
- uses: actions/checkout@v4

- uses: haskell-actions/setup@v2
id: setup
with:
ghc-version: ${{ matrix.ghc }}
cabal-version: latest
cabal-update: true

- name: Resolve dependencies
run: cabal build all --dry-run

- uses: actions/cache@v4
with:
path: ${{ steps.setup.outputs.cabal-store }}
key: cabal-${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-${{ hashFiles('dist-newstyle/cache/plan.json') }}
restore-keys: cabal-${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-

- name: Build
run: cabal build all

- name: Test
run: cabal test all --test-show-details=direct

- name: Documentation
run: cabal haddock typesafe-ai-core typesafe-ai

doctest:
name: Examples in the documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: haskell-actions/setup@v2
id: setup
with:
ghc-version: "9.12"
cabal-version: latest
cabal-update: true

- uses: actions/cache@v4
with:
path: ${{ steps.setup.outputs.cabal-store }}
key: doctest-${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-${{ hashFiles('**/*.cabal') }}
restore-keys: doctest-${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-

- name: Install doctest
run: cabal install doctest --ignore-project --overwrite-policy=always --installdir="$HOME/.local/bin"

# --with-repl (cabal 3.16 or later) runs doctest in place of GHCi while
# GHC still builds the dependencies. --with-compiler would make cabal
# rebuild the local typesafe-ai-core library with doctest, which fails.
- name: Run doctest
run: |
for package in typesafe-ai-core typesafe-ai; do
cabal repl "$package" --with-repl="$HOME/.local/bin/doctest" --repl-options=-w
done

hackage:
name: Hackage readiness
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: haskell-actions/setup@v2
id: setup
with:
ghc-version: "9.12"
cabal-version: latest
cabal-update: true

- name: cabal check
run: |
for package in typesafe-ai-core typesafe-ai; do
(cd "$package" && cabal check)
done

- name: Build and test from the source distributions
# Catches files missing from extra-source-files, such as the vendored
# OpenAPI specification the conformance tests read.
run: |
cabal sdist typesafe-ai-core typesafe-ai
mkdir "$RUNNER_TEMP/sdist"
for tarball in dist-newstyle/sdist/*.tar.gz; do
tar -xzf "$tarball" -C "$RUNNER_TEMP/sdist"
done
cd "$RUNNER_TEMP/sdist"
printf 'packages: */*.cabal\ntests: True\n' > cabal.project
cabal build all
cabal test all --test-show-details=direct
78 changes: 78 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Release

# Push a tag such as v0.1.0.0 to upload both packages to Hackage as package
# candidates. Review the candidates on Hackage, then run this workflow by hand
# with "publish" checked to publish them. See MAINTAINING.md.

on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
publish:
description: Publish the release on Hackage (otherwise upload candidates)
type: boolean
default: false

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
environment: hackage
steps:
- uses: actions/checkout@v4

- uses: haskell-actions/setup@v2
id: setup
with:
ghc-version: "9.12"
cabal-version: latest
cabal-update: true

- name: Check that the tag matches the package versions
if: startsWith(github.ref, 'refs/tags/')
run: |
tag="${GITHUB_REF_NAME#v}"
for package in typesafe-ai-core typesafe-ai; do
version="$(sed -n 's/^version: *//p' "$package/$package.cabal")"
if [ "$version" != "$tag" ]; then
echo "$package is version $version, but the tag is $GITHUB_REF_NAME" >&2
exit 1
fi
done

- name: Build and test
run: |
cabal build all
cabal test all --test-show-details=direct

- name: Package
run: |
for package in typesafe-ai-core typesafe-ai; do
(cd "$package" && cabal check)
done
cabal sdist typesafe-ai-core typesafe-ai
cabal haddock typesafe-ai-core typesafe-ai --haddock-for-hackage

- name: Upload to Hackage
env:
HACKAGE_TOKEN: ${{ secrets.HACKAGE_TOKEN }}
PUBLISH: ${{ inputs.publish && '--publish' || '' }}
run: |
# typesafe-ai depends on typesafe-ai-core, so the core goes first.
for package in typesafe-ai-core typesafe-ai; do
cabal upload $PUBLISH --token "$HACKAGE_TOKEN" dist-newstyle/sdist/"$package"-[0-9]*.tar.gz
cabal upload $PUBLISH --documentation --token "$HACKAGE_TOKEN" dist-newstyle/"$package"-[0-9]*-docs.tar.gz
done

- name: Create the GitHub release
if: inputs.publish
env:
GH_TOKEN: ${{ github.token }}
run: |
version="$(sed -n 's/^version: *//p' typesafe-ai/typesafe-ai.cabal)"
# The notes are the changelog section of this version.
awk -v v="$version" '$0 ~ "^## " v { found = 1; next } found && /^## / { exit } found' typesafe-ai/CHANGELOG.md > "$RUNNER_TEMP/notes.md"
gh release create "v$version" --title "v$version" --notes-file "$RUNNER_TEMP/notes.md"
87 changes: 87 additions & 0 deletions .github/workflows/spec-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: API specification drift

# Checks every day whether TypeSafe has published a new version of its OpenAPI
# specification. When it has, this opens (or updates) a pull request that
# replaces the vendored copy, with a summary of the changes and the list of
# conformance tests that fail against it: the to-do list for updating the
# bindings. See MAINTAINING.md.

on:
schedule:
- cron: "23 5 * * *"
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
drift:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Compare with the published specification
id: sync
run: |
if scripts/sync-spec.sh --summary "$RUNNER_TEMP/summary.md"; then
if [ -s "$RUNNER_TEMP/summary.md" ]; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
else
exit 1
fi

- uses: haskell-actions/setup@v2
if: steps.sync.outputs.changed == 'true'
id: setup
with:
ghc-version: "9.12"
cabal-version: latest
cabal-update: true

- uses: actions/cache@v4
if: steps.sync.outputs.changed == 'true'
with:
path: ${{ steps.setup.outputs.cabal-store }}
key: drift-${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-${{ hashFiles('**/*.cabal') }}
restore-keys: drift-${{ runner.os }}-ghc-${{ steps.setup.outputs.ghc-version }}-

- name: Run the conformance tests against the new specification
if: steps.sync.outputs.changed == 'true'
run: |
summary="$RUNNER_TEMP/summary.md"
set +e
cabal test typesafe-ai-core --test-show-details=direct > "$RUNNER_TEMP/tests.log" 2>&1
status=$?
set -e
{
echo "## Conformance tests"
echo
if [ "$status" -eq 0 ]; then
echo "All tests pass against the new specification. Review the changes above, bump \`apiSpecVersion\` if the version changed, and add typed support for any new features."
else
echo "These tests fail against the new specification. Each one names something the bindings do not cover yet:"
echo
echo '```'
grep -E 'FAIL|expected:|but got:|failed' "$RUNNER_TEMP/tests.log" | head -100
echo '```'
fi
echo
echo "See MAINTAINING.md for how to update the bindings."
} >> "$summary"

- uses: peter-evans/create-pull-request@v7
if: steps.sync.outputs.changed == 'true'
with:
# A token other than GITHUB_TOKEN lets the pull request trigger CI.
token: ${{ secrets.SPEC_SYNC_TOKEN || github.token }}
branch: api-spec-update
delete-branch: true
commit-message: Update the vendored TypeSafe OpenAPI specification
title: Update the vendored TypeSafe OpenAPI specification
body-path: ${{ runner.temp }}/summary.md
labels: api-update
add-paths: typesafe-ai-core/spec/openapi.json
Loading
Loading