# CI for shopdb-flask on GitHub Actions. # # Mirrors the internal CI pipeline. Four jobs on push + pull_request: # backend - pytest (SQLite via TestingConfig, no DB service needed) # naming - the CONTRIBUTING.md naming/style gate # frontend - vitest + Vue build # migrations-mysql - the REAL multi-site deploy path: fresh flask db upgrade # + every plugin's chain on utf8mb4 MySQL 8, idempotent on # a second run. The pytest suite only exercises SQLite # create_all(), so this is what catches an Alembic # regression on MySQL before it ships. name: CI on: push: pull_request: jobs: backend: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.13' cache: pip - run: pip install -r requirements.txt - run: python -m pytest -q naming: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - run: bash scripts/check-naming-and-style.sh frontend: runs-on: ubuntu-latest defaults: run: working-directory: frontend steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: npm cache-dependency-path: frontend/package-lock.json - run: npm ci - run: npx vitest run - run: npm run build lean-build: # ADR-013 Phase 5: prove a per-site build carries only its chosen plugins. # Builds a lean site (machines + printers) and asserts an omitted plugin's # code is absent from the bundle - the delete-a-plugin guarantee in CI. runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: '20' cache: npm cache-dependency-path: frontend/package-lock.json - run: cd frontend && npm ci - name: Build lean site (machines + printers) run: | printf '{ "site": "ci-lean", "plugins": ["machines", "printers"] }' \ > /tmp/lean-profile.json bash scripts/build-site.sh /tmp/lean-profile.json /tmp/leansite - name: Assert omitted plugin code is absent, chosen present run: | assets=/tmp/leansite/frontend-dist/assets for code in PartsKiosk ManifestEditor USBLabelBatch KnowledgeBaseDetail; do if grep -rqoh "$code" "$assets"/*.js; then echo "FAIL: omitted-plugin code '$code' leaked into the lean bundle" exit 1 fi done for code in MachineDetail PrinterDetail; do grep -rqoh "$code" "$assets"/*.js || { echo "FAIL: chosen-plugin code '$code' missing from the lean bundle" exit 1; } done # Core frontends (no manifest, e.g. applications) must ship in EVERY # build regardless of SITE_PLUGINS, or a lean site loses a core page. grep -rqoh "ApplicationsList" "$assets"/*.js || { echo "FAIL: core page 'ApplicationsList' missing from the lean bundle" exit 1; } test -d /tmp/leansite/plugins/machines test ! -d /tmp/leansite/plugins/printedparts echo "lean build verified: only chosen plugins present" migrations-mysql: runs-on: ubuntu-latest services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: shopdb_ci ports: - 3306:3306 options: >- --health-cmd="mysqladmin ping -h localhost -uroot -proot" --health-interval=5s --health-timeout=5s --health-retries=20 env: FLASK_APP: shopdb DATABASE_URL: mysql+pymysql://root:root@127.0.0.1:3306/shopdb_ci?charset=utf8mb4 SECRET_KEY: ci-secret JWT_SECRET_KEY: ci-jwt-secret steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: '3.13' cache: pip - run: pip install -r requirements.txt - name: Force utf8mb4 on the CI database # pymysql speaks MySQL 8's caching_sha2_password via the cryptography # package (a requirements.txt dependency), so no auth-plugin change # is needed here. run: | mysql -h 127.0.0.1 -uroot -proot -e \ "ALTER DATABASE shopdb_ci CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" - name: Fresh core upgrade from empty run: flask db upgrade - name: Install every bundled plugin (runs its chain) run: | for p in computers employees geenforce knowledgebase machines \ measuringtools network notifications printedparts printers \ slides usb warranty; do flask plugin install "$p" done flask plugin upgrade-all - name: Seed platform data (catches strict-mode data violations) # Runs the real seeders on strict MySQL 8. A seeded row that exceeds a # column width is a hard error 1406 here (not the silent truncation # older/relaxed MySQL gives), so this is what catches over-length # setting descriptions and similar before they reach a fresh box. run: | flask seed permissions flask seed settings flask seed reference-data - name: Assert schema built + utf8mb4 run: | python - <<'PY' from shopdb import create_app from shopdb.extensions import db from sqlalchemy import text app = create_app() with app.app_context(): tables = db.inspect(db.engine).get_table_names() assert len(tables) >= 70, f'only {len(tables)} tables built' row = db.session.execute(text( "SELECT default_character_set_name FROM information_schema.schemata " "WHERE schema_name = 'shopdb_ci'")).first() assert row[0] == 'utf8mb4', f'charset is {row[0]}, not utf8mb4' print(f'OK: {len(tables)} tables, charset {row[0]}') PY - name: Second core upgrade must be a clean no-op run: flask db upgrade