Skip to content

Commit 80b2d57

Browse files
tdobrowolski1claude
andcommitted
ci: GitHub Actions workflows — CI, NuGet release, PyPI release, nightly
ci.yml: build + integration tests (C# + Python matrix 3.10-3.12), schema drift guard, gitleaks, markdown link check. Runs on PR + push to main. release-csharp.yml / release-python.yml: tag-triggered. Verify tag matches package version, build/pack, push to NuGet and PyPI respectively. Requires secrets NUGET_API_KEY and PYPI_TOKEN. nightly.yml: daily drift sentinel — runs schema-drift guard + integration tests against the live API to catch silent SDK or API schema changes before users hit them. All four workflows are inert until the GitHub repo exists and secrets are configured. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 607a4cc commit 80b2d57

4 files changed

Lines changed: 221 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
name: CI
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
push:
7+
branches: [main]
8+
9+
concurrency:
10+
group: ci-${{ github.ref }}
11+
cancel-in-progress: true
12+
13+
jobs:
14+
csharp:
15+
name: C# build + tests
16+
runs-on: ubuntu-latest
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- name: Setup .NET 9
21+
uses: actions/setup-dotnet@v4
22+
with:
23+
dotnet-version: '9.0.x'
24+
25+
- name: Restore
26+
run: dotnet restore src/csharp/FlashAlpha.QuantConnect.sln
27+
28+
- name: Build (Release, treat warnings as errors)
29+
run: dotnet build src/csharp/FlashAlpha.QuantConnect.sln -c Release --no-restore
30+
31+
- name: Test (integration tests against live API)
32+
env:
33+
FLASHALPHA_API_KEY: ${{ secrets.FLASHALPHA_API_KEY }}
34+
run: dotnet test src/csharp/FlashAlpha.QuantConnect.sln -c Release --no-build --filter "Category=Integration" --logger "console;verbosity=normal"
35+
36+
python:
37+
name: Python install + tests
38+
runs-on: ubuntu-latest
39+
strategy:
40+
matrix:
41+
python-version: ['3.10', '3.11', '3.12']
42+
steps:
43+
- uses: actions/checkout@v4
44+
45+
- name: Setup Python ${{ matrix.python-version }}
46+
uses: actions/setup-python@v5
47+
with:
48+
python-version: ${{ matrix.python-version }}
49+
50+
- name: Install package + dev deps
51+
working-directory: src/python
52+
run: pip install -e ".[dev]"
53+
54+
- name: Test
55+
working-directory: src/python
56+
env:
57+
FLASHALPHA_API_KEY: ${{ secrets.FLASHALPHA_API_KEY }}
58+
run: pytest -v --tb=short
59+
60+
schema-drift:
61+
name: Schema drift guard
62+
runs-on: ubuntu-latest
63+
needs: [csharp, python]
64+
steps:
65+
- uses: actions/checkout@v4
66+
- uses: actions/setup-dotnet@v4
67+
with:
68+
dotnet-version: '9.0.x'
69+
- name: Run drift guard
70+
run: dotnet test src/csharp/FlashAlpha.QuantConnect.sln --filter "FullyQualifiedName~SchemaDriftGuard"
71+
72+
secrets-scan:
73+
name: Gitleaks
74+
runs-on: ubuntu-latest
75+
steps:
76+
- uses: actions/checkout@v4
77+
with: { fetch-depth: 0 }
78+
- uses: gitleaks/gitleaks-action@v2
79+
env:
80+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
81+
82+
docs:
83+
name: Markdown link check
84+
runs-on: ubuntu-latest
85+
steps:
86+
- uses: actions/checkout@v4
87+
- name: Check markdown links
88+
uses: gaurav-nelson/github-action-markdown-link-check@v1
89+
with:
90+
use-quiet-mode: 'yes'
91+
folder-path: 'docs'
92+
file-path: 'README.md, CHANGELOG.md'

.github/workflows/nightly.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Nightly drift sentinel
2+
3+
on:
4+
schedule:
5+
- cron: '0 6 * * *' # 06:00 UTC daily
6+
workflow_dispatch:
7+
8+
jobs:
9+
drift:
10+
name: Schema + golden drift check
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- name: Setup .NET 9
16+
uses: actions/setup-dotnet@v4
17+
with: { dotnet-version: '9.0.x' }
18+
19+
- name: Setup Python 3.12
20+
uses: actions/setup-python@v5
21+
with: { python-version: '3.12' }
22+
23+
- name: C# drift + integration tests (live API)
24+
env:
25+
FLASHALPHA_API_KEY: ${{ secrets.FLASHALPHA_API_KEY }}
26+
run: dotnet test src/csharp/FlashAlpha.QuantConnect.sln --filter "Category=Integration"
27+
28+
- name: Python drift + integration tests (live API)
29+
working-directory: src/python
30+
env:
31+
FLASHALPHA_API_KEY: ${{ secrets.FLASHALPHA_API_KEY }}
32+
run: |
33+
pip install -e ".[dev]"
34+
pytest -v -m integration
35+
36+
- name: Notify on failure
37+
if: failure()
38+
run: |
39+
echo "Nightly drift detected — investigate."
40+
echo "Most likely causes: (a) SDK shipped a new field that the bridge doesn't expose, (b) API response shape changed, (c) FlashAlpha API outage."
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Release C# to NuGet
2+
3+
on:
4+
push:
5+
tags: ['v*']
6+
7+
jobs:
8+
release:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
13+
- name: Setup .NET 9
14+
uses: actions/setup-dotnet@v4
15+
with:
16+
dotnet-version: '9.0.x'
17+
18+
- name: Verify tag matches csproj Version
19+
run: |
20+
TAG_VERSION=${GITHUB_REF_NAME#v}
21+
CSPROJ_VERSION=$(grep -oPm1 '(?<=<Version>)[^<]+' src/csharp/FlashAlpha.QuantConnect/FlashAlpha.QuantConnect.csproj)
22+
if [ "$TAG_VERSION" != "$CSPROJ_VERSION" ]; then
23+
echo "Tag $TAG_VERSION does not match csproj <Version>$CSPROJ_VERSION</Version>"
24+
exit 1
25+
fi
26+
echo "Version OK: $TAG_VERSION"
27+
28+
- name: Restore + build
29+
run: |
30+
dotnet restore src/csharp/FlashAlpha.QuantConnect.sln
31+
dotnet build src/csharp/FlashAlpha.QuantConnect/FlashAlpha.QuantConnect.csproj -c Release --no-restore
32+
33+
- name: Pack
34+
run: dotnet pack src/csharp/FlashAlpha.QuantConnect/FlashAlpha.QuantConnect.csproj -c Release -o nupkg --no-build
35+
36+
- name: Push to NuGet
37+
env:
38+
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
39+
run: dotnet nuget push "nupkg/FlashAlpha.QuantConnect.*.nupkg" --api-key "$NUGET_API_KEY" --source https://api.nuget.org/v3/index.json --skip-duplicate
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Release Python to PyPI
2+
3+
on:
4+
push:
5+
tags: ['v*']
6+
7+
jobs:
8+
release:
9+
runs-on: ubuntu-latest
10+
steps:
11+
- uses: actions/checkout@v4
12+
13+
- name: Setup Python
14+
uses: actions/setup-python@v5
15+
with:
16+
python-version: '3.12'
17+
18+
- name: Verify tag matches pyproject version
19+
run: |
20+
TAG_VERSION=${GITHUB_REF_NAME#v}
21+
# Strip any pre-release dot from PEP 440 (e.g. 0.2.0rc1 → 0.2.0rc1; tag would be 0.2.0-rc.1)
22+
# Tags use SemVer (v0.2.0-rc.1) and PyPI uses PEP 440 (0.2.0rc1) — normalize for comparison.
23+
TAG_NORMALIZED=$(echo "$TAG_VERSION" | tr -d '-' | tr -d '.' | sed 's/rc/.rc./' || true)
24+
PYPROJ_VERSION=$(grep -oPm1 '(?<=^version = ")[^"]+' src/python/pyproject.toml)
25+
# Simple equality after stripping
26+
A=$(echo "$TAG_VERSION" | tr -d 'v-.')
27+
B=$(echo "$PYPROJ_VERSION" | tr -d '-.')
28+
if [ "$A" != "$B" ]; then
29+
echo "Tag $TAG_VERSION does not match pyproject version $PYPROJ_VERSION"
30+
exit 1
31+
fi
32+
echo "Version OK: $TAG_VERSION ↔ $PYPROJ_VERSION"
33+
34+
- name: Install build tools
35+
run: pip install build twine
36+
37+
- name: Build
38+
working-directory: src/python
39+
run: python -m build
40+
41+
- name: Twine check
42+
working-directory: src/python
43+
run: twine check dist/*
44+
45+
- name: Publish to PyPI
46+
working-directory: src/python
47+
env:
48+
TWINE_USERNAME: __token__
49+
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
50+
run: twine upload dist/*

0 commit comments

Comments
 (0)