05/19 Enhance codes 4
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
# .gitea/workflows/ci.yml
|
||||
#
|
||||
# PassKeeper CI pipeline — runs on every push and pull request.
|
||||
#
|
||||
# Jobs:
|
||||
# lint-python — flake8 style + error check
|
||||
# syntax-check — ast.parse all Python files (catches import-time errors)
|
||||
# migration-check — verify Alembic chain has a single head, no duplicates
|
||||
# js-syntax — node --check on all JS files
|
||||
# build-extension — zip the extension for distribution
|
||||
|
||||
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: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install flake8
|
||||
run: pip install flake8
|
||||
|
||||
- name: Run flake8
|
||||
run: |
|
||||
flake8 app/ \
|
||||
--max-line-length=120 \
|
||||
--extend-ignore=E501,W503 \
|
||||
--exclude=__pycache__,migrations \
|
||||
--statistics
|
||||
|
||||
# ── Python syntax (ast.parse — catches broken imports fast) ─────────────────
|
||||
syntax-check:
|
||||
name: Python syntax check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- 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)
|
||||
print(f"OK: {len(list(pathlib.Path('app').rglob('*.py')))} Python files parsed cleanly")
|
||||
EOF
|
||||
|
||||
# ── Alembic migration chain check ───────────────────────────────────────────
|
||||
migration-check:
|
||||
name: Alembic migration chain
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install alembic
|
||||
run: pip install alembic
|
||||
|
||||
- name: Verify single head, linear chain, 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: Set up Node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: "20"
|
||||
|
||||
- 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 --input-type=module < "$f" 2>/dev/null || \
|
||||
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 extension: $(du -sh ../passkeeper-extension-chrome.zip | cut -f1)"
|
||||
|
||||
- name: Build Firefox extension
|
||||
run: |
|
||||
cd extension
|
||||
# Swap manifests for Firefox build
|
||||
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 extension: $(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
|
||||
Reference in New Issue
Block a user