Skip to content
Open
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
37 changes: 37 additions & 0 deletions .github/workflows/backend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Backend CI

on:
push:
branches: [dev, qa, main]
paths:
- 'server/**'
pull_request:
branches: [dev, qa, main]
paths:
- 'server/**'

jobs:
check:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4

- name: Install dependencies
run: pnpm install --no-frozen-lockfile

- name: Type check
run: pnpm --filter server run typecheck

- name: Lint
run: pnpm --filter server run lint

- name: Test
run: pnpm --filter server run test

- name: Build
run: pnpm --filter server run build
57 changes: 57 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Monorepo CI

on:
push:
branches: [dev, qa, main]
pull_request:
branches: [dev, qa, main]

jobs:
changes:
runs-on: ubuntu-latest
outputs:
admin: ${{ steps.filter.outputs.admin }}
client: ${{ steps.filter.outputs.client }}
server: ${{ steps.filter.outputs.server }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
admin: 'admin/**'
client: 'client/**'
server: 'server/**'

build:
needs: changes
strategy:
matrix:
component: [admin, client, server]
runs-on: ubuntu-latest
if: |
(matrix.component == 'admin' && needs.changes.outputs.admin == 'true') ||
(matrix.component == 'client' && needs.changes.outputs.client == 'true') ||
(matrix.component == 'server' && needs.changes.outputs.server == 'true')
defaults:
run:
working-directory: ${{ matrix.component }}
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
cache: "pnpm"
cache-dependency-path: ${{ matrix.component }}/pnpm-lock.yaml

- run: pnpm install --no-frozen-lockfile --recursive

- name: Type check (server only)
if: matrix.component == 'server'
run: pnpm exec tsc --noEmit

- run: pnpm run lint
- run: pnpm run test
- run: pnpm run build
67 changes: 67 additions & 0 deletions .github/workflows/enforce-pr-source.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
name: Enforce PR Source Branches

on:
pull_request:
branches: [qa, main]
types: [opened, synchronize, reopened, edited]

jobs:
check-qa-source:
name: Check PRs targeting `qa`
if: github.base_ref == 'qa'
runs-on: ubuntu-latest
steps:
- name: Verify source branch is `dev`
run: |
if [[ "${{ github.head_ref }}" != "dev" ]]; then
echo "❌ ERROR: PRs to 'qa' must come from 'dev'!"
echo ""
echo "Valid path to qa:"
echo " dev → qa"
echo ""
echo "Your PR is from: ${{ github.head_ref }}"
echo "Expected source: dev"
echo ""
echo "Please:"
echo "1. Close this PR"
echo "2. Merge your changes into 'dev' branch first"
echo "3. Create a new PR from 'dev' to 'qa'"
exit 1
fi

- name: Optional – Suggest release preparation
run: |
echo "✅ PR from dev to qa is valid. Continue with QA testing."

check-main-source:
name: Check PRs targeting `main`
if: github.base_ref == 'main'
runs-on: ubuntu-latest
steps:
- name: Verify source branch is `qa`
run: |
if [[ "${{ github.head_ref }}" != "qa" ]]; then
echo "❌ ERROR: Direct PR to 'main' is not allowed!"
echo ""
echo "Valid path to main:"
echo " dev → qa → main"
echo ""
echo "Your PR is from: ${{ github.head_ref }}"
echo "Expected source: qa"
echo ""
echo "Please:"
echo "1. Close this PR"
echo "2. Merge your changes into 'qa' branch first"
echo "3. Create a new PR from 'qa' to 'main'"
exit 1
fi

- name: Enforce release version naming (optional)
run: |
TITLE="${{ github.event.pull_request.title }}"
if [[ ! "$TITLE" =~ ^Release\ version\ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "⚠️ Suggestion: PR title should follow 'Release version X.Y.Z'"
# Change to "exit 1" if you want to enforce the naming strictly
else
echo "✅ Valid release title format."
fi
78 changes: 78 additions & 0 deletions .github/workflows/frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Frontend CI

on:
push:
branches: [dev, qa, main]
paths:
- "admin/**"
- "client/**"
- ".github/workflows/frontend.yml"
pull_request:
branches: [dev, qa, main]
paths:
- "admin/**"
- "client/**"
- ".github/workflows/frontend.yml"

jobs:
changes:
runs-on: ubuntu-latest
outputs:
admin: ${{ steps.filter.outputs.admin }}
client: ${{ steps.filter.outputs.client }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
admin:
- 'admin/**'
client:
- 'client/**'

build-admin:
needs: changes
if: needs.changes.outputs.admin == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: admin
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
cache: "pnpm"
cache-dependency-path: admin/pnpm-lock.yaml

- run: pnpm install --no-frozen-lockfile
- run: pnpm exec tsc --noEmit
- run: pnpm run lint
- run: pnpm run test
- run: pnpm run build

build-client:
needs: changes
if: needs.changes.outputs.client == 'true'
runs-on: ubuntu-latest
defaults:
run:
working-directory: client
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
cache: "pnpm"
cache-dependency-path: client/pnpm-lock.yaml

- run: pnpm install --no-frozen-lockfile
- run: pnpm exec tsc --noEmit
- run: pnpm run lint
- run: pnpm run test
- run: pnpm run build
52 changes: 52 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Logs
*.log
lerna-debug.log*
logs
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*

# Build / Distribution
dist
dist-ssr

# Editor / IDE
.agents
.agent
.idea
.vscode
!.vscode/extensions.json
*.njsproj
*.ntvs*
*.sln
*.suo
*.sw?

# Root
node_modules/

# System / OS
.DS_Store

# -------------------------------------

# Admin
/admin/.env
/admin/.vite/
/admin/dist/
/admin/node_modules

# Client
/client/.env
/client/.vite/
/client/dist/
/client/node_modules

# Server
/server/.env
/server/dist/
/server/node_modules

# Test coverage
coverage/
1 change: 1 addition & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pnpm exec lint-staged
96 changes: 96 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/bin/sh

# Pre-push hook — runs full typecheck + lint + build + test only on the sub-projects that changed.
# Reads from stdin in the format: <local-ref> <local-sha1> <remote-ref> <remote-sha1>

changed_files=""
while read local_ref local_sha remote_ref remote_sha; do
# Skip branch deletion
if [ "$local_sha" = "0000000000000000000000000000000000000000" ]; then
continue
fi

# Determine what to compare against
if [ "$remote_sha" = "0000000000000000000000000000000000000000" ]; then
# New branch — compare against merge-base with main or dev
against=$(git merge-base HEAD main 2>/dev/null || git merge-base HEAD dev 2>/dev/null || echo "$local_sha~1")
else
against="$remote_sha"
fi

new_changed=$(git diff --name-only "$against".."$local_sha" 2>/dev/null)
if [ -n "$new_changed" ]; then
changed_files="$changed_files
$new_changed"
fi
done

if [ -z "$changed_files" ] || [ "$changed_files" = "" ]; then
echo "✅ No file changes detected, skipping pre-push checks."
exit 0
fi

# Categorize changed files by sub-project
CLIENT_FILES=""
ADMIN_FILES=""
SERVER_FILES=""

while IFS= read -r file; do
[ -z "$file" ] && continue

[ ! -f "$file" ] && continue
case "$file" in
client/*) CLIENT_FILES="$CLIENT_FILES $file" ;;
admin/*) ADMIN_FILES="$ADMIN_FILES $file" ;;
server/*) SERVER_FILES="$SERVER_FILES $file" ;;
esac
done <<EOF
$(echo "$changed_files" | sed '/^$/d')
EOF

failed=0
ROOT_DIR="$(pwd)"

echo ""
echo "═══════════════════════════════════════"
echo " Pre-push checks"
echo "═══════════════════════════════════════"

if [ -n "$CLIENT_FILES" ]; then
echo ""
echo "📦 Checking client …"
CLIENT_FILES_RELATIVE=$(printf "%s" "$CLIENT_FILES" | sed 's|^[[:space:]]*||; s| client/| |g; s|^client/||')
(cd "$ROOT_DIR/client" && pnpm exec eslint --no-warn-ignored $CLIENT_FILES_RELATIVE 2>&1) || failed=1
(cd "$ROOT_DIR/client" && pnpm test 2>&1) || failed=1
(cd "$ROOT_DIR/client" && pnpm build 2>&1) || failed=1
echo " Client checks complete"
fi

if [ -n "$ADMIN_FILES" ]; then
echo ""
echo "📦 Checking admin …"
ADMIN_FILES_RELATIVE=$(printf "%s" "$ADMIN_FILES" | sed 's|^[[:space:]]*||; s| admin/| |g; s|^admin/||')
(cd "$ROOT_DIR/admin" && pnpm exec eslint --no-warn-ignored $ADMIN_FILES_RELATIVE 2>&1) || failed=1
(cd "$ROOT_DIR/admin" && pnpm test 2>&1) || failed=1
(cd "$ROOT_DIR/admin" && pnpm build 2>&1) || failed=1
echo " Admin checks complete"
fi

if [ -n "$SERVER_FILES" ]; then
echo ""
echo "📦 Checking server …"
SERVER_FILES_RELATIVE=$(printf "%s" "$SERVER_FILES" | sed 's|^[[:space:]]*||; s| server/| |g; s|^server/||')
(cd "$ROOT_DIR/server" && pnpm exec eslint --no-warn-ignored $SERVER_FILES_RELATIVE 2>&1) || failed=1
(cd "$ROOT_DIR/server" && pnpm test 2>&1) || failed=1
(cd "$ROOT_DIR/server" && pnpm build 2>&1) || failed=1
echo " Server checks complete"
fi

echo ""
echo "───────────────────────────────────────"
if [ "$failed" = 1 ]; then
echo "❌ Pre-push checks FAILED — push blocked."
exit 1
fi
echo "✅ All checks passed — push allowed."
exit 0
Binary file added BMV full logo nontransparent.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added BMV full logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading