Files
PassKeeper/.gitea/workflows/ci.yml
T
2026-05-19 11:13:25 -04:00

174 lines
6.2 KiB
YAML

# .gitea/workflows/ci.yml
#
# PassKeeper CI pipeline — runs on every push and pull request.
# Designed for a self-hosted host-mode runner (no Docker required).
# Requires on the host: python3, pip3, node, zip
#
# Jobs:
# lint-python — flake8 style + error check
# syntax-check — ast.parse all Python files
# migration-check — verify Alembic chain has single head
# js-syntax — node syntax check on all JS files
# build-extension — zip Chrome and Firefox extensions
name: CI
on:
push:
branches: ["main", "master", "dev"]
pull_request:
branches: ["main", "master"]
jobs:
# ── Python lint ──────────────────────────────────────────────────────────────
lint-python:
name: Python lint (flake8)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install flake8
run: pip3 install flake8 --quiet --break-system-packages
- name: Run flake8
run: |
flake8 app/ \
--max-line-length=120 \
--extend-ignore=E501,W503 \
--exclude=__pycache__,migrations \
--statistics
# ── Python syntax ────────────────────────────────────────────────────────────
syntax-check:
name: Python syntax check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check all Python files parse cleanly
run: |
python3 - << 'EOF'
import ast, sys, pathlib
failures = []
for path in pathlib.Path('app').rglob('*.py'):
try:
ast.parse(path.read_text())
except SyntaxError as e:
failures.append(f"{path}: {e}")
for f in failures:
print(f"FAIL: {f}")
if failures:
sys.exit(1)
count = len(list(pathlib.Path('app').rglob('*.py')))
print(f"OK: {count} Python files parsed cleanly")
EOF
# ── Alembic migration chain ──────────────────────────────────────────────────
migration-check:
name: Alembic migration chain
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Verify single head, no duplicate revisions
run: |
python3 - << 'EOF'
import re, sys, glob
files = glob.glob('migrations/versions/*.py')
revisions = {}
for f in files:
content = open(f).read()
rev = re.search(r"revision = '([^']+)'", content)
down = re.search(r"down_revision = (.+)", content)
if rev:
rid = rev.group(1)
if rid in revisions:
print(f"FAIL: Duplicate revision ID {rid} in {f}")
sys.exit(1)
revisions[rid] = down.group(1).strip() if down else 'None'
all_downs = set(revisions.values())
heads = [r for r in revisions if repr(r) not in all_downs and r not in all_downs]
if len(heads) != 1:
print(f"FAIL: Expected 1 head, found {len(heads)}: {heads}")
sys.exit(1)
print(f"OK: {len(revisions)} migrations, single head: {heads[0]}")
EOF
# ── JS syntax check ──────────────────────────────────────────────────────────
js-syntax:
name: JavaScript syntax check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Check JS files parse cleanly
run: |
FAILED=0
for f in \
app/static/js/vault.js \
app/static/js/auth.js \
app/static/js/crypto.js \
app/static/js/sharing.js \
app/static/js/recover.js \
extension/popup/popup.js \
extension/background.js \
extension/background.firefox.js \
extension/content/content.js \
extension/bridge/bridge.js \
extension/shared/crypto.js; do
if [ -f "$f" ]; then
node -e "new Function(require('fs').readFileSync('$f','utf8'))" 2>/dev/null || \
{ echo "FAIL: $f"; FAILED=1; }
fi
done
[ $FAILED -eq 0 ] && echo "OK: all JS files parsed cleanly"
exit $FAILED
# ── Extension build ──────────────────────────────────────────────────────────
build-extension:
name: Build extension zip
runs-on: ubuntu-latest
needs: [syntax-check, js-syntax]
steps:
- uses: actions/checkout@v3
- name: Build Chrome/Edge extension
run: |
cd extension
zip -r ../passkeeper-extension-chrome.zip . \
--exclude "*.bak" \
--exclude "manifest.firefox.json" \
--exclude "background.firefox.js"
echo "Chrome: $(du -sh ../passkeeper-extension-chrome.zip | cut -f1)"
- name: Build Firefox extension
run: |
cd extension
cp manifest.json manifest.chrome.json
cp manifest.firefox.json manifest.json
zip -r ../passkeeper-extension-firefox.zip . \
--exclude "*.bak" \
--exclude "manifest.chrome.json" \
--exclude "background.js"
mv manifest.chrome.json manifest.json
echo "Firefox: $(du -sh ../passkeeper-extension-firefox.zip | cut -f1)"
- name: Upload Chrome extension artifact
uses: actions/upload-artifact@v3
with:
name: passkeeper-extension-chrome
path: passkeeper-extension-chrome.zip
retention-days: 30
- name: Upload Firefox extension artifact
uses: actions/upload-artifact@v3
with:
name: passkeeper-extension-firefox
path: passkeeper-extension-firefox.zip
retention-days: 30