mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
Replace manual docker compose start/stop in the CI workflow with Testcontainers in Playwright global setup/teardown. This gives: - Automatic container lifecycle tied to the test run - Health-check-based wait strategies per service - Clean teardown even on test failures - No CI workflow coupling to Docker orchestration Changes: - e2e/global.setup.ts: Start smoke stack via DockerComposeEnvironment (nginx, phpMyAdmin, Mongo Express, RedisInsight) with health check waits - e2e/global.teardown.ts: New file — stops Testcontainers environment - e2e/playwright.config.ts: Register globalSetup/globalTeardown, bind dev servers to 0.0.0.0 in CI so nginx can proxy via host.docker.internal - gated-pipeline.yml: Remove docker compose start/stop/verify steps, add 10min timeout to Playwright step - e2e/deployment-smoke.spec.ts: Update doc comment - package.json: Add testcontainers@^11.12.0 devDependency https://claude.ai/code/session_018rmhuicK7L7jV2YBJDXiQz
1815 lines
66 KiB
YAML
1815 lines
66 KiB
YAML
name: Enterprise Gated Pipeline
|
|
|
|
on:
|
|
push:
|
|
branches: [ main, master, develop ]
|
|
tags:
|
|
- 'v*.*.*'
|
|
pull_request:
|
|
branches: [ main, master, develop ]
|
|
types: [opened, synchronize, ready_for_review, edited, reopened]
|
|
release:
|
|
types: [published]
|
|
issues:
|
|
types: [opened, edited, reopened]
|
|
issue_comment:
|
|
types: [created]
|
|
workflow_dispatch:
|
|
inputs:
|
|
environment:
|
|
description: 'Target deployment environment'
|
|
required: true
|
|
type: choice
|
|
options:
|
|
- staging
|
|
- production
|
|
skip_tests:
|
|
description: 'Skip pre-deployment tests (emergency only)'
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
run_codeql:
|
|
description: 'Run CodeQL semantic analysis'
|
|
required: false
|
|
type: boolean
|
|
default: false
|
|
codeql_languages:
|
|
description: 'CodeQL languages to analyze'
|
|
required: false
|
|
type: choice
|
|
options:
|
|
- all
|
|
- javascript-typescript
|
|
- python
|
|
- cpp
|
|
- go
|
|
default: all
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE_NAME: ${{ github.repository }}
|
|
|
|
permissions:
|
|
contents: read
|
|
pull-requests: write
|
|
checks: write
|
|
statuses: write
|
|
issues: write
|
|
deployments: write
|
|
packages: write
|
|
id-token: write
|
|
attestations: write
|
|
security-events: write
|
|
|
|
# ════════════════════════════════════════════════════════════════════════════════
|
|
# Unified Enterprise Gated Pipeline — One YML to Rule Them All
|
|
# ════════════════════════════════════════════════════════════════════════════════
|
|
#
|
|
# Standalone (event-guarded, run independently of gates):
|
|
# - Triage: Auto-label issues and PRs (issues/PR events only)
|
|
# - CodeQL: Semantic code analysis (manual dispatch only)
|
|
#
|
|
# Sequential Gates (fan-out/fan-in):
|
|
# Gate 1: Code Quality (DBAL schemas, typecheck, lint, security)
|
|
# Gate 2: Testing (unit with coverage, E2E, DBAL daemon)
|
|
# Gate 3: Build & Package
|
|
# Gate 4: Development Assistance (PR only)
|
|
# Gate 5: Staging Deployment (main branch push)
|
|
# Gate 6: Production Deployment (release or manual with approval)
|
|
# Gate 7: Container Build & Push (push/tag/dispatch, not PRs)
|
|
# ════════════════════════════════════════════════════════════════════════════════
|
|
|
|
jobs:
|
|
# ============================================================================
|
|
# TRIAGE: Auto-label Issues and PRs (standalone, event-guarded)
|
|
# ============================================================================
|
|
|
|
triage-issue:
|
|
name: "Triage: Auto-Label Issue"
|
|
if: github.event_name == 'issues'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Categorize and label issue
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const issue = context.payload.issue;
|
|
const title = (issue.title || '').toLowerCase();
|
|
const body = (issue.body || '').toLowerCase();
|
|
const text = `${title}\n${body}`;
|
|
|
|
const labels = new Set();
|
|
const missing = [];
|
|
|
|
const typeMatchers = [
|
|
{ regex: /bug|error|crash|broken|fail/, label: 'bug' },
|
|
{ regex: /feature|enhancement|add|new|implement/, label: 'enhancement' },
|
|
{ regex: /document|readme|docs|guide/, label: 'documentation' },
|
|
{ regex: /test|testing|spec|e2e/, label: 'testing' },
|
|
{ regex: /security|vulnerability|exploit|xss|sql/, label: 'security' },
|
|
{ regex: /performance|slow|optimize|speed/, label: 'performance' },
|
|
];
|
|
|
|
for (const match of typeMatchers) {
|
|
if (text.match(match.regex)) {
|
|
labels.add(match.label);
|
|
}
|
|
}
|
|
|
|
const areaMatchers = [
|
|
{ regex: /frontend|react|next|ui|component|browser/, label: 'area: frontend' },
|
|
{ regex: /api|backend|service|server/, label: 'area: backend' },
|
|
{ regex: /database|prisma|schema|sql/, label: 'area: database' },
|
|
{ regex: /workflow|github actions|ci|pipeline/, label: 'area: workflows' },
|
|
{ regex: /docs|readme|guide/, label: 'area: documentation' },
|
|
];
|
|
|
|
for (const match of areaMatchers) {
|
|
if (text.match(match.regex)) {
|
|
labels.add(match.label);
|
|
}
|
|
}
|
|
|
|
if (text.match(/critical|urgent|asap|blocker/)) {
|
|
labels.add('priority: high');
|
|
} else if (text.match(/minor|low|nice to have/)) {
|
|
labels.add('priority: low');
|
|
} else {
|
|
labels.add('priority: medium');
|
|
}
|
|
|
|
if (text.match(/beginner|easy|simple|starter/) || labels.size <= 2) {
|
|
labels.add('good first issue');
|
|
}
|
|
|
|
const reproductionHints = ['steps to reproduce', 'expected', 'actual'];
|
|
for (const hint of reproductionHints) {
|
|
if (!body.includes(hint)) {
|
|
missing.push(hint);
|
|
}
|
|
}
|
|
|
|
const supportInfo = body.includes('version') || body.match(/v\d+\.\d+/);
|
|
if (!supportInfo) {
|
|
missing.push('version information');
|
|
}
|
|
|
|
if (labels.size > 0) {
|
|
await github.rest.issues.addLabels({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
labels: Array.from(labels),
|
|
}).catch(e => console.log('Some labels may not exist:', e.message));
|
|
}
|
|
|
|
const checklist = missing.map(item => `- [ ] Add ${item}`).join('\n') || '- [x] Description includes key details.';
|
|
const summary = Array.from(labels).map(l => `- ${l}`).join('\n') || '- No labels inferred yet.';
|
|
|
|
const comment = [
|
|
'Thanks for reporting an issue! Quick triage:',
|
|
'',
|
|
'**Proposed labels:**',
|
|
summary,
|
|
'',
|
|
'**Missing details:**',
|
|
checklist,
|
|
'',
|
|
'Adding the missing details will help reviewers respond faster.',
|
|
].join('\n');
|
|
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
body: comment,
|
|
});
|
|
|
|
triage-pr:
|
|
name: "Triage: Auto-Label PR"
|
|
if: github.event_name == 'pull_request'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Analyze PR files and label
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const pr = context.payload.pull_request;
|
|
const { data: files } = await github.rest.pulls.listFiles({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: pr.number,
|
|
});
|
|
|
|
const labels = new Set();
|
|
|
|
const fileFlags = {
|
|
workflows: files.some(f => f.filename.includes('.github/workflows')),
|
|
docs: files.some(f => f.filename.match(/\.(md|mdx)$/) || f.filename.startsWith('docs/')),
|
|
frontend: files.some(f => f.filename.includes('frontends/nextjs')),
|
|
db: files.some(f => f.filename.includes('prisma/') || f.filename.includes('dbal/')),
|
|
tests: files.some(f => f.filename.match(/(test|spec)\.[jt]sx?/)),
|
|
};
|
|
|
|
if (fileFlags.workflows) labels.add('area: workflows');
|
|
if (fileFlags.docs) labels.add('area: documentation');
|
|
if (fileFlags.frontend) labels.add('area: frontend');
|
|
if (fileFlags.db) labels.add('area: database');
|
|
if (fileFlags.tests) labels.add('tests');
|
|
|
|
const totalChanges = files.reduce((sum, f) => sum + f.additions + f.deletions, 0);
|
|
const highRiskPaths = files.filter(f => f.filename.includes('.github/workflows') || f.filename.includes('prisma/'));
|
|
|
|
let riskLabel = 'risk: low';
|
|
if (highRiskPaths.length > 0 || totalChanges >= 400) {
|
|
riskLabel = 'risk: high';
|
|
} else if (totalChanges >= 150) {
|
|
riskLabel = 'risk: medium';
|
|
}
|
|
labels.add(riskLabel);
|
|
|
|
const missing = [];
|
|
const body = (pr.body || '').toLowerCase();
|
|
if (!body.includes('test')) missing.push('Test plan');
|
|
if (fileFlags.frontend && !body.includes('screenshot')) missing.push('Screenshots for UI changes');
|
|
if (!body.match(/#\d+|https:\/\/github\.com/)) missing.push('Linked issue reference');
|
|
|
|
if (labels.size > 0) {
|
|
await github.rest.issues.addLabels({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: pr.number,
|
|
labels: Array.from(labels),
|
|
}).catch(e => console.log('Some labels may not exist:', e.message));
|
|
}
|
|
|
|
const labelSummary = Array.from(labels).map(l => `- ${l}`).join('\n');
|
|
const missingList = missing.length ? missing.map(item => `- [ ] ${item}`).join('\n') : '- [x] Description includes required context.';
|
|
|
|
const comment = [
|
|
'**Automated PR triage**',
|
|
'',
|
|
'**Proposed labels:**',
|
|
labelSummary,
|
|
'',
|
|
'**Description check:**',
|
|
missingList,
|
|
'',
|
|
'If any labels look incorrect, feel free to adjust them.',
|
|
].join('\n');
|
|
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: pr.number,
|
|
body: comment,
|
|
});
|
|
|
|
# ============================================================================
|
|
# GATE 1: Code Quality Gates
|
|
# ============================================================================
|
|
|
|
gate-1-start:
|
|
name: "Gate 1: Code Quality - Starting"
|
|
runs-on: ubuntu-latest
|
|
if: |
|
|
github.event_name != 'issue_comment' &&
|
|
(github.event_name != 'pull_request' || !github.event.pull_request.draft)
|
|
steps:
|
|
- name: Gate 1 checkpoint
|
|
run: |
|
|
echo "Gate 1: CODE QUALITY VALIDATION"
|
|
echo "================================================"
|
|
echo "Running validation steps..."
|
|
|
|
- name: Create gate artifacts directory
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
echo "started" > gate-artifacts/gate-1/status.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-1/start-time.txt
|
|
|
|
- name: Upload gate start marker
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-1-start
|
|
path: gate-artifacts/gate-1/
|
|
|
|
# Atomic Step 1.1: DBAL Entity Schema Validation
|
|
schema-check:
|
|
name: "Gate 1.1: Validate DBAL Entity Schemas"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-1-start
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup Python
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: Validate DBAL entity JSON schemas
|
|
run: |
|
|
python3 -c "
|
|
import json, sys, os, glob
|
|
|
|
schema_dir = 'dbal/shared/api/schema/entities'
|
|
errors = []
|
|
total_entities = 0
|
|
|
|
# 1. Validate entities.json registry exists and refs resolve
|
|
registry = os.path.join(schema_dir, 'entities.json')
|
|
if not os.path.exists(registry):
|
|
errors.append('Missing entities.json registry')
|
|
else:
|
|
with open(registry) as f:
|
|
reg = json.load(f)
|
|
refs = [e['\$ref'] for e in reg.get('entities', [])]
|
|
for ref in refs:
|
|
path = os.path.join(schema_dir, ref.lstrip('./'))
|
|
if not os.path.exists(path):
|
|
errors.append(f'Registry ref not found: {ref}')
|
|
|
|
# 2. Validate each entity JSON file
|
|
jsons = glob.glob(os.path.join(schema_dir, '**/*.json'), recursive=True)
|
|
entity_files = [f for f in jsons if os.path.basename(f) != 'entities.json']
|
|
|
|
def validate_doc(doc, rel):
|
|
if not doc or not isinstance(doc, dict):
|
|
errors.append(f'{rel}: empty or non-mapping document')
|
|
return
|
|
has_name = any(k in doc for k in ('entity', 'name', 'displayName'))
|
|
if not has_name:
|
|
errors.append(f'{rel}: missing entity/name/displayName key')
|
|
if 'fields' not in doc:
|
|
errors.append(f'{rel}: missing fields key')
|
|
elif not isinstance(doc['fields'], dict):
|
|
errors.append(f'{rel}: fields must be a mapping')
|
|
else:
|
|
for fname, fdef in doc['fields'].items():
|
|
if not isinstance(fdef, dict) or 'type' not in fdef:
|
|
errors.append(f'{rel}: field {fname} missing type')
|
|
|
|
for filepath in sorted(entity_files):
|
|
rel = os.path.relpath(filepath, schema_dir)
|
|
try:
|
|
with open(filepath) as f:
|
|
doc = json.load(f)
|
|
if isinstance(doc, list):
|
|
for i, item in enumerate(doc):
|
|
validate_doc(item, f'{rel}[{i}]')
|
|
total_entities += 1
|
|
else:
|
|
validate_doc(doc, rel)
|
|
total_entities += 1
|
|
except json.JSONDecodeError as e:
|
|
errors.append(f'{rel}: JSON parse error: {e}')
|
|
|
|
print(f'Checked {len(entity_files)} files, {total_entities} entities')
|
|
if errors:
|
|
print(f'Found {len(errors)} error(s):')
|
|
for e in errors:
|
|
print(f' ERROR: {e}')
|
|
sys.exit(1)
|
|
else:
|
|
print('All DBAL entity schemas valid')
|
|
"
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
echo "${{ job.status }}" > gate-artifacts/gate-1/schema-check.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-1/schema-check-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-1-schema-result
|
|
path: gate-artifacts/gate-1/
|
|
|
|
# Atomic Step 1.2: TypeScript Check
|
|
typecheck:
|
|
name: "Gate 1.2: TypeScript Type Check"
|
|
runs-on: ubuntu-latest
|
|
needs: schema-check
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup npm with Nexus
|
|
uses: ./.github/actions/setup-npm
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Build workspace packages
|
|
run: npm run build --workspaces --if-present 2>&1
|
|
|
|
- name: Run TypeScript type check
|
|
run: npm run typecheck -w frontends/nextjs
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
echo "${{ job.status }}" > gate-artifacts/gate-1/typecheck.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-1/typecheck-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-1-typecheck-result
|
|
path: gate-artifacts/gate-1/
|
|
|
|
# Atomic Step 1.3: ESLint
|
|
lint:
|
|
name: "Gate 1.3: Lint Code"
|
|
runs-on: ubuntu-latest
|
|
needs: schema-check
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup npm with Nexus
|
|
uses: ./.github/actions/setup-npm
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Build workspace packages
|
|
run: npm run build --workspaces --if-present 2>&1
|
|
|
|
- name: Run ESLint
|
|
run: |
|
|
set -o pipefail
|
|
cd frontends/nextjs
|
|
npx eslint . 2>&1 | tee /tmp/lint-out.txt || true
|
|
# Count errors in local src/ only (skip workspace transitive errors)
|
|
LOCAL_ERRORS=$(grep -cE " error " /tmp/lint-out.txt 2>/dev/null || echo "0")
|
|
echo "Total lint issues: $LOCAL_ERRORS"
|
|
# Allow up to 1500 issues (pre-existing workspace type-safety warnings)
|
|
if [ "$LOCAL_ERRORS" -gt 1500 ]; then
|
|
echo "::error::Too many lint errors ($LOCAL_ERRORS > 1500 threshold)"
|
|
exit 1
|
|
fi
|
|
echo "Lint: passed with $LOCAL_ERRORS issues (within threshold)"
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
echo "${{ job.status }}" > gate-artifacts/gate-1/lint.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-1/lint-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-1-lint-result
|
|
path: gate-artifacts/gate-1/
|
|
|
|
# Atomic Step 1.4: Security Scan
|
|
security-scan:
|
|
name: "Gate 1.4: Security Scan"
|
|
runs-on: ubuntu-latest
|
|
needs: schema-check
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup npm with Nexus
|
|
uses: ./.github/actions/setup-npm
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Run dependency audit
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
npm audit --json > gate-artifacts/gate-1/audit-results.json 2>&1
|
|
echo "Security audit completed"
|
|
continue-on-error: true
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
echo "${{ job.status }}" > gate-artifacts/gate-1/security-scan.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-1/security-scan-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-1-security-result
|
|
path: gate-artifacts/gate-1/
|
|
|
|
# Atomic Step 1.5: File Size Check
|
|
file-size-check:
|
|
name: "Gate 1.5: File Size Check"
|
|
runs-on: ubuntu-latest
|
|
needs: schema-check
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Check for oversized files
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
LARGE_FILES=$(find frontends/ components/ hooks/ redux/ -name "*.ts" -o -name "*.tsx" 2>/dev/null | xargs wc -l 2>/dev/null | awk '$1 > 500 {print $2}' | head -20)
|
|
if [ -n "$LARGE_FILES" ]; then
|
|
echo "Large files (>500 LOC):" | tee gate-artifacts/gate-1/file-sizes.txt
|
|
echo "$LARGE_FILES" | tee -a gate-artifacts/gate-1/file-sizes.txt
|
|
else
|
|
echo "No oversized files found" > gate-artifacts/gate-1/file-sizes.txt
|
|
fi
|
|
continue-on-error: true
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
echo "${{ job.status }}" > gate-artifacts/gate-1/file-size-check.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-1/file-size-check-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-1-filesize-result
|
|
path: gate-artifacts/gate-1/
|
|
|
|
# Atomic Step 1.6: Code Complexity Check
|
|
code-complexity-check:
|
|
name: "Gate 1.6: Code Complexity Check"
|
|
runs-on: ubuntu-latest
|
|
needs: schema-check
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Check code complexity
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
# Count files by category
|
|
FRONTEND_FILES=$(find frontends/ -name "*.ts" -o -name "*.tsx" 2>/dev/null | wc -l)
|
|
COMPONENT_FILES=$(find components/ -name "*.ts" -o -name "*.tsx" 2>/dev/null | wc -l)
|
|
REDUX_FILES=$(find redux/ -name "*.ts" -o -name "*.tsx" 2>/dev/null | wc -l)
|
|
HOOK_FILES=$(find hooks/ -name "*.ts" -o -name "*.tsx" 2>/dev/null | wc -l)
|
|
echo "Frontends: $FRONTEND_FILES files" | tee gate-artifacts/gate-1/complexity.txt
|
|
echo "Components: $COMPONENT_FILES files" | tee -a gate-artifacts/gate-1/complexity.txt
|
|
echo "Redux: $REDUX_FILES files" | tee -a gate-artifacts/gate-1/complexity.txt
|
|
echo "Hooks: $HOOK_FILES files" | tee -a gate-artifacts/gate-1/complexity.txt
|
|
continue-on-error: true
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
echo "${{ job.status }}" > gate-artifacts/gate-1/complexity-check.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-1/complexity-check-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-1-complexity-result
|
|
path: gate-artifacts/gate-1/
|
|
|
|
# Atomic Step 1.7: Stub Detection
|
|
stub-detection:
|
|
name: "Gate 1.7: Detect Stub Implementations"
|
|
runs-on: ubuntu-latest
|
|
needs: schema-check
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Detect stubs and placeholder code
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
# Check for common stub patterns
|
|
STUBS=$(grep -rn "TODO\|FIXME\|HACK\|XXX\|not implemented\|throw new Error.*not implemented" \
|
|
--include="*.ts" --include="*.tsx" \
|
|
frontends/ components/ hooks/ redux/ 2>/dev/null | head -50)
|
|
if [ -n "$STUBS" ]; then
|
|
echo "Potential stubs/TODOs found:" > gate-artifacts/gate-1/stubs.txt
|
|
echo "$STUBS" >> gate-artifacts/gate-1/stubs.txt
|
|
else
|
|
echo "No stub implementations detected" > gate-artifacts/gate-1/stubs.txt
|
|
fi
|
|
continue-on-error: true
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
echo "${{ job.status }}" > gate-artifacts/gate-1/stub-detection.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-1/stub-detection-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-1-stub-result
|
|
path: gate-artifacts/gate-1/
|
|
|
|
gate-1-complete:
|
|
name: "Gate 1: Code Quality - Passed"
|
|
runs-on: ubuntu-latest
|
|
needs: [schema-check, typecheck, lint, security-scan, file-size-check, code-complexity-check, stub-detection]
|
|
steps:
|
|
- name: Download all gate 1 artifacts
|
|
uses: actions/download-artifact@v6
|
|
with:
|
|
pattern: gate-1-*
|
|
path: gate-artifacts/
|
|
merge-multiple: true
|
|
|
|
- name: Generate Gate 1 summary
|
|
run: |
|
|
echo "GATE 1 PASSED: CODE QUALITY"
|
|
echo "================================================"
|
|
echo "1.1 DBAL entity schemas validated"
|
|
echo "1.2 TypeScript types checked"
|
|
echo "1.3 Code linted"
|
|
echo "1.4 Security scan completed"
|
|
echo "1.5 File sizes checked"
|
|
echo "1.6 Code complexity analyzed"
|
|
echo "1.7 Stub implementations detected"
|
|
echo ""
|
|
echo "Proceeding to Gate 2: Testing..."
|
|
|
|
- name: Create consolidated gate report
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-1
|
|
echo "completed" > gate-artifacts/gate-1/status.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-1/end-time.txt
|
|
ls -la gate-artifacts/gate-1/
|
|
|
|
- name: Upload consolidated gate 1 report
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-1-complete-report
|
|
path: gate-artifacts/
|
|
|
|
# ============================================================================
|
|
# GATE 2: Testing Gates
|
|
# ============================================================================
|
|
|
|
gate-2-start:
|
|
name: "Gate 2: Testing - Starting"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-1-complete
|
|
steps:
|
|
- name: Gate 2 checkpoint
|
|
run: |
|
|
echo "Gate 2: TESTING VALIDATION"
|
|
echo "================================================"
|
|
|
|
- name: Create gate artifacts directory
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-2
|
|
echo "started" > gate-artifacts/gate-2/status.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-2/start-time.txt
|
|
|
|
- name: Upload gate start marker
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-2-start
|
|
path: gate-artifacts/gate-2/
|
|
|
|
# Atomic Step 2.1: Unit Tests
|
|
test-unit:
|
|
name: "Gate 2.1: Unit Tests"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-2-start
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup npm with Nexus
|
|
uses: ./.github/actions/setup-npm
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Run unit tests with coverage
|
|
run: |
|
|
set -o pipefail
|
|
cd frontends/nextjs
|
|
npx vitest run --coverage --coverage.reporter=text --coverage.reporter=json-summary 2>&1 | tee /tmp/vitest-out.txt
|
|
FAILED=$(grep -oP '\d+ failed' /tmp/vitest-out.txt | head -1 | grep -oP '\d+' || echo "0")
|
|
PASSED=$(grep -oP '\d+ passed' /tmp/vitest-out.txt | head -1 | grep -oP '\d+' || echo "0")
|
|
echo "Tests: $PASSED passed, $FAILED failed"
|
|
if [ "$FAILED" -gt 20 ]; then
|
|
echo "::error::Too many test failures ($FAILED > 20 threshold)"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Check coverage thresholds
|
|
run: |
|
|
cd frontends/nextjs
|
|
if [ -f coverage/coverage-summary.json ]; then
|
|
python3 -c "
|
|
import json, sys
|
|
with open('coverage/coverage-summary.json') as f:
|
|
data = json.load(f)
|
|
total = data['total']
|
|
for metric in ['lines', 'functions', 'branches', 'statements']:
|
|
pct = total[metric]['pct']
|
|
print(f'{metric}: {pct}%')
|
|
lines_pct = total['lines']['pct']
|
|
if lines_pct < 10:
|
|
print(f'::error::Line coverage {lines_pct}% is below 10% minimum')
|
|
sys.exit(1)
|
|
print(f'Coverage gate passed: {lines_pct}% lines covered')
|
|
"
|
|
else
|
|
echo "::warning::No coverage report generated — coverage check skipped"
|
|
fi
|
|
|
|
- name: Upload coverage report
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: coverage-report
|
|
path: frontends/nextjs/coverage/
|
|
retention-days: 7
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-2
|
|
echo "${{ job.status }}" > gate-artifacts/gate-2/test-unit.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-2/test-unit-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-2-unit-result
|
|
path: gate-artifacts/gate-2/
|
|
|
|
# Atomic Step 2.2: E2E Tests
|
|
test-e2e:
|
|
name: "Gate 2.2: E2E Tests"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-2-start
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup npm with Nexus
|
|
uses: ./.github/actions/setup-npm
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Build workspace packages
|
|
run: npm run build --workspaces --if-present 2>&1
|
|
|
|
- name: Install Playwright Browsers
|
|
run: npx playwright install --with-deps chromium
|
|
|
|
- name: Run Playwright tests
|
|
run: |
|
|
if [ -f e2e/playwright.config.ts ]; then
|
|
npx playwright test --config=e2e/playwright.config.ts 2>&1
|
|
else
|
|
echo "::warning::No playwright.config.ts found — E2E tests not configured"
|
|
fi
|
|
timeout-minutes: 10
|
|
|
|
- name: Upload test results
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: playwright-report
|
|
path: playwright-report/
|
|
retention-days: 7
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-2
|
|
echo "${{ job.status }}" > gate-artifacts/gate-2/test-e2e.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-2/test-e2e-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-2-e2e-result
|
|
path: gate-artifacts/gate-2/
|
|
|
|
# Atomic Step 2.3: DBAL Daemon Tests
|
|
test-dbal-daemon:
|
|
name: "Gate 2.3: DBAL Daemon E2E"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-2-start
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup npm with Nexus
|
|
uses: ./.github/actions/setup-npm
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Install Playwright Browsers
|
|
run: npx playwright install --with-deps chromium
|
|
|
|
- name: Run DBAL daemon suite
|
|
run: |
|
|
if [ -f e2e/playwright.dbal-daemon.config.ts ]; then
|
|
npx playwright test --config=e2e/playwright.dbal-daemon.config.ts 2>&1
|
|
else
|
|
echo "::warning::No DBAL daemon playwright config found — tests not configured"
|
|
fi
|
|
|
|
- name: Upload daemon test report
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: playwright-report-dbal-daemon
|
|
path: frontends/nextjs/playwright-report/
|
|
retention-days: 7
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-2
|
|
echo "${{ job.status }}" > gate-artifacts/gate-2/test-dbal-daemon.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-2/test-dbal-daemon-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-2-dbal-result
|
|
path: gate-artifacts/gate-2/
|
|
|
|
gate-2-complete:
|
|
name: "Gate 2: Testing - Passed"
|
|
runs-on: ubuntu-latest
|
|
needs: [test-unit, test-e2e, test-dbal-daemon]
|
|
steps:
|
|
- name: Download all gate 2 artifacts
|
|
uses: actions/download-artifact@v6
|
|
with:
|
|
pattern: gate-2-*
|
|
path: gate-artifacts/
|
|
merge-multiple: true
|
|
|
|
- name: Generate Gate 2 summary
|
|
run: |
|
|
echo "GATE 2 PASSED: TESTING"
|
|
echo "================================================"
|
|
echo "2.1 Unit tests passed"
|
|
echo "2.2 E2E tests passed"
|
|
echo "2.3 DBAL daemon tests passed"
|
|
echo ""
|
|
echo "Proceeding to Gate 3: Build & Package..."
|
|
|
|
- name: Create consolidated gate report
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-2
|
|
echo "completed" > gate-artifacts/gate-2/status.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-2/end-time.txt
|
|
ls -la gate-artifacts/gate-2/
|
|
|
|
- name: Upload consolidated gate 2 report
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-2-complete-report
|
|
path: gate-artifacts/
|
|
|
|
# ============================================================================
|
|
# GATE 3: Build & Package Gates
|
|
# ============================================================================
|
|
|
|
gate-3-start:
|
|
name: "Gate 3: Build & Package - Starting"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-2-complete
|
|
steps:
|
|
- name: Gate 3 checkpoint
|
|
run: |
|
|
echo "Gate 3: BUILD & PACKAGE VALIDATION"
|
|
echo "================================================"
|
|
|
|
- name: Create gate artifacts directory
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-3
|
|
echo "started" > gate-artifacts/gate-3/status.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-3/start-time.txt
|
|
|
|
- name: Upload gate start marker
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-3-start
|
|
path: gate-artifacts/gate-3/
|
|
|
|
# Atomic Step 3.1: Build Application
|
|
build:
|
|
name: "Gate 3.1: Build Application"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-3-start
|
|
outputs:
|
|
build-success: ${{ steps.build-step.outcome }}
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup npm with Nexus
|
|
uses: ./.github/actions/setup-npm
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Build workspace packages
|
|
run: npm run build --workspaces --if-present 2>&1
|
|
|
|
- name: Build
|
|
id: build-step
|
|
run: npm run build -w frontends/nextjs
|
|
|
|
- name: Upload build artifacts
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: dist
|
|
path: frontends/nextjs/.next/
|
|
retention-days: 7
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-3
|
|
echo "${{ job.status }}" > gate-artifacts/gate-3/build.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-3/build-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-3-build-result
|
|
path: gate-artifacts/gate-3/
|
|
|
|
# Atomic Step 3.2: Quality Metrics
|
|
quality-check:
|
|
name: "Gate 3.2: Code Quality Metrics"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-3-start
|
|
if: github.event_name == 'pull_request'
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Check for console.log statements
|
|
run: |
|
|
if git diff origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' | grep -E '^\+.*console\.(log|debug|info)'; then
|
|
echo "Found console.log statements in the changes"
|
|
echo "Please remove console.log statements before merging"
|
|
exit 1
|
|
fi
|
|
continue-on-error: true
|
|
|
|
- name: Check for TODO comments
|
|
run: |
|
|
TODO_COUNT=$(git diff origin/${{ github.base_ref }}...HEAD -- '*.ts' '*.tsx' '*.js' '*.jsx' | grep -E '^\+.*TODO|FIXME' | wc -l)
|
|
if [ $TODO_COUNT -gt 0 ]; then
|
|
echo "Found $TODO_COUNT TODO/FIXME comments in the changes"
|
|
fi
|
|
continue-on-error: true
|
|
|
|
- name: Record validation result
|
|
if: always()
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-3
|
|
echo "${{ job.status }}" > gate-artifacts/gate-3/quality-check.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-3/quality-check-time.txt
|
|
|
|
- name: Upload validation result
|
|
if: always()
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-3-quality-result
|
|
path: gate-artifacts/gate-3/
|
|
|
|
gate-3-complete:
|
|
name: "Gate 3: Build & Package - Passed"
|
|
runs-on: ubuntu-latest
|
|
needs: [build, quality-check]
|
|
if: always() && needs.build.result == 'success' && (needs.quality-check.result == 'success' || needs.quality-check.result == 'skipped')
|
|
steps:
|
|
- name: Download all gate 3 artifacts
|
|
uses: actions/download-artifact@v6
|
|
with:
|
|
pattern: gate-3-*
|
|
path: gate-artifacts/
|
|
merge-multiple: true
|
|
|
|
- name: Generate Gate 3 summary
|
|
run: |
|
|
echo "GATE 3 PASSED: BUILD & PACKAGE"
|
|
echo "================================================"
|
|
echo "3.1 Application built successfully"
|
|
echo "3.2 Quality metrics validated"
|
|
|
|
- name: Create consolidated gate report
|
|
run: |
|
|
mkdir -p gate-artifacts/gate-3
|
|
echo "completed" > gate-artifacts/gate-3/status.txt
|
|
echo "$(date -Iseconds)" > gate-artifacts/gate-3/end-time.txt
|
|
ls -la gate-artifacts/gate-3/
|
|
|
|
- name: Upload consolidated gate 3 report
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: gate-3-complete-report
|
|
path: gate-artifacts/
|
|
|
|
# ============================================================================
|
|
# GATE 4: Development Assistance (PR Only)
|
|
# ============================================================================
|
|
|
|
gate-4-dev-feedback:
|
|
name: "Gate 4: Development Assistance"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-3-complete
|
|
if: github.event_name == 'pull_request' && !github.event.pull_request.draft
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Analyze code metrics
|
|
id: quality
|
|
run: |
|
|
TOTAL_TS_FILES=$(find frontends/ components/ hooks/ redux/ -name "*.ts" -o -name "*.tsx" 2>/dev/null | wc -l)
|
|
LARGE_FILES=$(find frontends/ components/ hooks/ redux/ -name "*.ts" -o -name "*.tsx" -exec wc -l {} \; 2>/dev/null | awk '$1 > 150 {print $2}' | wc -l)
|
|
JSON_FILES=$(find frontends/ packages/ -name "*.json" 2>/dev/null | wc -l)
|
|
|
|
echo "total_ts_files=$TOTAL_TS_FILES" >> $GITHUB_OUTPUT
|
|
echo "large_files=$LARGE_FILES" >> $GITHUB_OUTPUT
|
|
echo "json_files=$JSON_FILES" >> $GITHUB_OUTPUT
|
|
|
|
- name: Check architectural compliance
|
|
id: architecture
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
let issues = [];
|
|
let suggestions = [];
|
|
|
|
// Get changed files
|
|
let changedFiles = [];
|
|
if (context.eventName === 'pull_request') {
|
|
const { data: files } = await github.rest.pulls.listFiles({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
pull_number: context.issue.number,
|
|
});
|
|
changedFiles = files.map(f => f.filename);
|
|
}
|
|
|
|
// Check for hardcoded components
|
|
const hardcodedComponents = changedFiles.filter(f =>
|
|
f.endsWith('.tsx') &&
|
|
f.includes('src/components/') &&
|
|
!f.includes('src/components/ui/') &&
|
|
!f.includes('src/components/shared/') &&
|
|
!['RenderComponent', 'FieldRenderer', 'GenericPage'].some(g => f.includes(g))
|
|
);
|
|
|
|
if (hardcodedComponents.length > 0) {
|
|
suggestions.push(`Consider if these components could be declarative: ${hardcodedComponents.join(', ')}`);
|
|
}
|
|
|
|
// Check for DBAL entity schema changes
|
|
const schemaChanged = changedFiles.some(f => f.includes('dbal/shared/api/schema/entities/'));
|
|
if (schemaChanged) {
|
|
suggestions.push('DBAL entity schemas changed — verify DBAL daemon compatibility.');
|
|
}
|
|
|
|
// Check for large files
|
|
const largeFiles = parseInt('${{ steps.quality.outputs.large_files }}');
|
|
if (largeFiles > 0) {
|
|
issues.push(`${largeFiles} TypeScript files exceed 150 lines.`);
|
|
}
|
|
|
|
return { issues, suggestions };
|
|
|
|
- name: Provide development feedback
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const analysis = JSON.parse('${{ steps.architecture.outputs.result }}');
|
|
const totalFiles = parseInt('${{ steps.quality.outputs.total_ts_files }}');
|
|
const largeFiles = parseInt('${{ steps.quality.outputs.large_files }}');
|
|
const jsonFiles = parseInt('${{ steps.quality.outputs.json_files }}');
|
|
|
|
let comment = `## Gate 4: Development Feedback\n\n`;
|
|
|
|
comment += `### Code Metrics\n\n`;
|
|
comment += `- TypeScript files: ${totalFiles}\n`;
|
|
comment += `- Files >150 LOC: ${largeFiles} ${largeFiles > 0 ? '(warning)' : '(ok)'}\n`;
|
|
comment += `- JSON config files: ${jsonFiles}\n`;
|
|
comment += `- Declarative ratio: ${((jsonFiles) / Math.max(totalFiles, 1) * 100).toFixed(1)}%\n\n`;
|
|
|
|
if (analysis.issues.length > 0) {
|
|
comment += `### Issues\n\n`;
|
|
analysis.issues.forEach(issue => comment += `- ${issue}\n`);
|
|
comment += '\n';
|
|
}
|
|
|
|
if (analysis.suggestions.length > 0) {
|
|
comment += `### Suggestions\n\n`;
|
|
analysis.suggestions.forEach(suggestion => comment += `- ${suggestion}\n`);
|
|
comment += '\n';
|
|
}
|
|
|
|
comment += `Gate 4 Complete - Development feedback provided\n`;
|
|
|
|
// Check if we already commented
|
|
const { data: comments } = await github.rest.issues.listComments({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: context.issue.number,
|
|
});
|
|
|
|
const botComment = comments.find(c =>
|
|
c.user.type === 'Bot' && c.body.includes('Gate 4: Development Feedback')
|
|
);
|
|
|
|
if (botComment) {
|
|
await github.rest.issues.updateComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
comment_id: botComment.id,
|
|
body: comment
|
|
});
|
|
} else {
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: context.issue.number,
|
|
body: comment
|
|
});
|
|
}
|
|
|
|
# Handle Copilot mentions in comments
|
|
gate-4-copilot-interaction:
|
|
name: "Gate 4: Copilot Interaction"
|
|
runs-on: ubuntu-latest
|
|
if: |
|
|
github.event_name == 'issue_comment' &&
|
|
contains(github.event.comment.body, '@copilot')
|
|
steps:
|
|
- name: Parse Copilot request
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const comment = context.payload.comment.body.toLowerCase();
|
|
const issue = context.payload.issue;
|
|
|
|
let response = `## Copilot Assistance\n\n`;
|
|
|
|
if (comment.includes('help') || !comment.match(/(implement|review|architecture|test)/)) {
|
|
response += `Mention **@copilot** with:\n`;
|
|
response += `- \`@copilot implement this\` - Implementation guidance\n`;
|
|
response += `- \`@copilot review this\` - Code review\n`;
|
|
response += `- \`@copilot architecture\` - Architecture guidance\n`;
|
|
response += `- \`@copilot test this\` - Testing guidance\n\n`;
|
|
}
|
|
|
|
response += `*Use GitHub Copilot in your IDE with project context for best results.*`;
|
|
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: issue.number,
|
|
body: response
|
|
});
|
|
|
|
# ============================================================================
|
|
# GATE 5: Staging Deployment (Main Branch Only)
|
|
# ============================================================================
|
|
|
|
gate-5-staging-deploy:
|
|
name: "Gate 5: Staging Deployment"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-3-complete
|
|
if: |
|
|
github.event_name == 'push' &&
|
|
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master')
|
|
environment:
|
|
name: staging
|
|
url: https://staging.metabuilder.example.com
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup npm with Nexus
|
|
uses: ./.github/actions/setup-npm
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Build for staging
|
|
run: npm run build -w frontends/nextjs
|
|
env:
|
|
NEXT_PUBLIC_ENV: staging
|
|
|
|
- name: Deploy to staging
|
|
run: |
|
|
echo "Deploying to staging environment..."
|
|
echo "Build artifacts ready for deployment"
|
|
|
|
- name: Run smoke tests
|
|
run: |
|
|
echo "Running smoke tests on staging..."
|
|
echo "Basic health checks completed"
|
|
|
|
# ============================================================================
|
|
# GATE 6: Production Deployment (Release/Manual with Approval)
|
|
# ============================================================================
|
|
|
|
gate-6-production-gate:
|
|
name: "Gate 6: Production Approval Gate"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-3-complete
|
|
if: |
|
|
github.event_name == 'release' ||
|
|
(github.event_name == 'workflow_dispatch' && inputs.environment == 'production')
|
|
steps:
|
|
- name: Pre-production checklist
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
console.log('Production Deployment Gate');
|
|
console.log('Requires manual approval in GitHub Actions UI');
|
|
|
|
gate-6-production-deploy:
|
|
name: "Gate 6: Production Deployment"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-6-production-gate
|
|
environment:
|
|
name: production
|
|
url: https://metabuilder.example.com
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Setup npm with Nexus
|
|
uses: ./.github/actions/setup-npm
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Build for production
|
|
run: npm run build -w frontends/nextjs
|
|
env:
|
|
NEXT_PUBLIC_ENV: production
|
|
NODE_ENV: production
|
|
|
|
- name: Deploy to production
|
|
run: |
|
|
echo "Deploying to production environment..."
|
|
echo "Build artifacts ready for deployment"
|
|
|
|
- name: Run smoke tests
|
|
run: |
|
|
echo "Running smoke tests on production..."
|
|
echo "Production health checks completed"
|
|
|
|
- name: Post deployment summary
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
console.log('Gate 6: Production Deployment Complete');
|
|
|
|
await github.rest.issues.create({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
title: `Production Deployment - ${new Date().toISOString().split('T')[0]}`,
|
|
body: `Production deployed at ${new Date().toISOString()}`,
|
|
labels: ['deployment', 'production']
|
|
});
|
|
|
|
# ============================================================================
|
|
# GATE 7: Container Build & Push (push/tag/dispatch only, not PRs)
|
|
# ════════════════════════════════════════════════════════════════════════════
|
|
# Tiered base images respecting the dependency DAG:
|
|
# Tier 1 (independent): base-apt, base-node-deps, base-pip-deps
|
|
# Tier 2 (← base-apt): base-conan-deps, base-android-sdk
|
|
# Tier 3 (← all above): devcontainer
|
|
# App images build after T1, pulling base images from GHCR.
|
|
# ════════════════════════════════════════════════════════════════════════════
|
|
# ============================================================================
|
|
|
|
# ── Tier 1: Independent base images ─────────────────────────────────────────
|
|
container-base-tier1:
|
|
name: "Gate 7 T1: ${{ matrix.image }}"
|
|
runs-on: ubuntu-latest
|
|
needs: gate-3-complete
|
|
if: github.event_name != 'pull_request' && github.event_name != 'issues' && github.event_name != 'issue_comment'
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- image: base-apt
|
|
dockerfile: ./deployment/base-images/Dockerfile.apt
|
|
platforms: linux/amd64,linux/arm64
|
|
- image: base-node-deps
|
|
dockerfile: ./deployment/base-images/Dockerfile.node-deps
|
|
platforms: linux/amd64,linux/arm64
|
|
- image: base-pip-deps
|
|
dockerfile: ./deployment/base-images/Dockerfile.pip-deps
|
|
platforms: linux/amd64,linux/arm64
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@v4
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v4
|
|
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@v4
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract metadata (tags, labels)
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}
|
|
tags: |
|
|
type=ref,event=branch
|
|
type=semver,pattern={{version}}
|
|
type=semver,pattern={{major}}.{{minor}}
|
|
type=semver,pattern={{major}}
|
|
type=sha,prefix={{branch}}-
|
|
type=raw,value=latest,enable={{is_default_branch}}
|
|
|
|
- name: Build and push Docker image
|
|
id: build
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
file: ${{ matrix.dockerfile }}
|
|
platforms: ${{ matrix.platforms }}
|
|
push: ${{ github.event_name != 'pull_request' }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha,scope=${{ matrix.image }}
|
|
cache-to: type=gha,mode=max,scope=${{ matrix.image }}
|
|
build-args: |
|
|
BUILD_DATE=${{ github.event.head_commit.timestamp || github.run_started_at }}
|
|
VCS_REF=${{ github.sha }}
|
|
|
|
- name: Generate artifact attestation
|
|
uses: actions/attest-build-provenance@v4
|
|
with:
|
|
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}
|
|
subject-digest: ${{ steps.build.outputs.digest }}
|
|
push-to-registry: true
|
|
|
|
# ── Tier 2: Images depending on base-apt ────────────────────────────────────
|
|
container-base-tier2:
|
|
name: "Gate 7 T2: ${{ matrix.image }}"
|
|
runs-on: ubuntu-latest
|
|
needs: container-base-tier1
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- image: base-conan-deps
|
|
dockerfile: ./deployment/base-images/Dockerfile.conan-deps
|
|
platforms: linux/amd64,linux/arm64
|
|
- image: base-android-sdk
|
|
dockerfile: ./deployment/base-images/Dockerfile.android-sdk
|
|
platforms: linux/amd64,linux/arm64
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@v4
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v4
|
|
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@v4
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract metadata (tags, labels)
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}
|
|
tags: |
|
|
type=ref,event=branch
|
|
type=semver,pattern={{version}}
|
|
type=semver,pattern={{major}}.{{minor}}
|
|
type=semver,pattern={{major}}
|
|
type=sha,prefix={{branch}}-
|
|
type=raw,value=latest,enable={{is_default_branch}}
|
|
|
|
- name: Build and push Docker image
|
|
id: build
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
file: ${{ matrix.dockerfile }}
|
|
platforms: ${{ matrix.platforms }}
|
|
push: ${{ github.event_name != 'pull_request' }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha,scope=${{ matrix.image }}
|
|
cache-to: type=gha,mode=max,scope=${{ matrix.image }}
|
|
build-args: |
|
|
BASE_REGISTRY=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
BUILD_DATE=${{ github.event.head_commit.timestamp || github.run_started_at }}
|
|
VCS_REF=${{ github.sha }}
|
|
|
|
- name: Generate artifact attestation
|
|
uses: actions/attest-build-provenance@v4
|
|
with:
|
|
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}
|
|
subject-digest: ${{ steps.build.outputs.digest }}
|
|
push-to-registry: true
|
|
|
|
# ── Tier 3: Devcontainer (depends on all base images) ──────────────────────
|
|
container-base-tier3:
|
|
name: "Gate 7 T3: devcontainer"
|
|
runs-on: ubuntu-latest
|
|
needs: container-base-tier2
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up QEMU
|
|
uses: docker/setup-qemu-action@v4
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v4
|
|
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@v4
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract metadata (tags, labels)
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/devcontainer
|
|
tags: |
|
|
type=ref,event=branch
|
|
type=semver,pattern={{version}}
|
|
type=semver,pattern={{major}}.{{minor}}
|
|
type=semver,pattern={{major}}
|
|
type=sha,prefix={{branch}}-
|
|
type=raw,value=latest,enable={{is_default_branch}}
|
|
|
|
- name: Build and push Docker image
|
|
id: build
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: .
|
|
file: ./deployment/base-images/Dockerfile.devcontainer
|
|
platforms: linux/amd64,linux/arm64
|
|
push: ${{ github.event_name != 'pull_request' }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha,scope=devcontainer
|
|
cache-to: type=gha,mode=max,scope=devcontainer
|
|
build-args: |
|
|
BASE_REGISTRY=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
BUILD_DATE=${{ github.event.head_commit.timestamp || github.run_started_at }}
|
|
VCS_REF=${{ github.sha }}
|
|
|
|
- name: Generate artifact attestation
|
|
uses: actions/attest-build-provenance@v4
|
|
with:
|
|
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/devcontainer
|
|
subject-digest: ${{ steps.build.outputs.digest }}
|
|
push-to-registry: true
|
|
|
|
# ── App images (pull base images from GHCR) ────────────────────────────────
|
|
container-build-apps:
|
|
name: "Gate 7 App: ${{ matrix.image }}"
|
|
runs-on: ubuntu-latest
|
|
needs: [container-base-tier1]
|
|
if: github.event_name != 'pull_request' && github.event_name != 'issues' && github.event_name != 'issue_comment' && !failure()
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- image: nextjs-app
|
|
context: .
|
|
dockerfile: ./frontends/nextjs/Dockerfile
|
|
- image: codegen
|
|
context: .
|
|
dockerfile: ./frontends/codegen/Dockerfile
|
|
- image: pastebin
|
|
context: .
|
|
dockerfile: ./frontends/pastebin/Dockerfile
|
|
- image: emailclient
|
|
context: .
|
|
dockerfile: ./frontends/emailclient/Dockerfile
|
|
- image: postgres-dashboard
|
|
context: .
|
|
dockerfile: ./frontends/postgres/Dockerfile
|
|
- image: workflowui
|
|
context: .
|
|
dockerfile: ./frontends/workflowui/Dockerfile
|
|
- image: exploded-diagrams
|
|
context: .
|
|
dockerfile: ./frontends/exploded-diagrams/Dockerfile
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@v4
|
|
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@v4
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract metadata (tags, labels)
|
|
id: meta
|
|
uses: docker/metadata-action@v5
|
|
with:
|
|
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}
|
|
tags: |
|
|
type=ref,event=branch
|
|
type=semver,pattern={{version}}
|
|
type=semver,pattern={{major}}.{{minor}}
|
|
type=semver,pattern={{major}}
|
|
type=sha,prefix=sha-
|
|
type=raw,value=latest,enable={{is_default_branch}}
|
|
|
|
- name: Build and push Docker image
|
|
id: build
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: ${{ matrix.context }}
|
|
file: ${{ matrix.dockerfile }}
|
|
push: ${{ github.event_name != 'pull_request' }}
|
|
tags: ${{ steps.meta.outputs.tags }}
|
|
labels: ${{ steps.meta.outputs.labels }}
|
|
cache-from: type=gha,scope=${{ matrix.image }}
|
|
cache-to: type=gha,mode=max,scope=${{ matrix.image }}
|
|
build-args: |
|
|
BASE_REGISTRY=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
|
BUILD_DATE=${{ github.event.head_commit.timestamp || github.run_started_at }}
|
|
VCS_REF=${{ github.sha }}
|
|
VERSION=${{ steps.meta.outputs.version }}
|
|
|
|
- name: Generate artifact attestation
|
|
uses: actions/attest-build-provenance@v4
|
|
with:
|
|
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}
|
|
subject-digest: ${{ steps.build.outputs.digest }}
|
|
push-to-registry: true
|
|
|
|
# ── Container security scanning ────────────────────────────────────────────
|
|
container-security-scan:
|
|
name: "Gate 7 Scan: ${{ matrix.image }}"
|
|
runs-on: ubuntu-latest
|
|
needs: [container-base-tier3, container-build-apps]
|
|
if: github.event_name != 'pull_request' && github.event_name != 'issues' && github.event_name != 'issue_comment' && !failure()
|
|
continue-on-error: true # heavy/not-yet-built images must not block the gate
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
image:
|
|
- base-apt
|
|
- base-node-deps
|
|
- base-pip-deps
|
|
- base-conan-deps
|
|
- base-android-sdk
|
|
- devcontainer
|
|
- nextjs-app
|
|
- codegen
|
|
- pastebin
|
|
- emailclient
|
|
- postgres-dashboard
|
|
- workflowui
|
|
- exploded-diagrams
|
|
steps:
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@v4
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Run Trivy vulnerability scanner
|
|
uses: aquasecurity/trivy-action@0.35.0
|
|
timeout-minutes: 15
|
|
with:
|
|
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}/${{ matrix.image }}:${{ github.ref_name }}
|
|
format: 'sarif'
|
|
output: 'trivy-results-${{ matrix.image }}.sarif'
|
|
|
|
- name: Upload Trivy results to GitHub Security tab
|
|
uses: github/codeql-action/upload-sarif@v4
|
|
with:
|
|
sarif_file: 'trivy-results-${{ matrix.image }}.sarif'
|
|
category: container-${{ matrix.image }}
|
|
|
|
# ── Multi-arch manifests ────────────────────────────────────────────────────
|
|
container-publish-manifest:
|
|
name: "Gate 7: Multi-Arch Manifests"
|
|
runs-on: ubuntu-latest
|
|
needs: [container-base-tier3, container-build-apps]
|
|
if: github.event_name != 'pull_request' && github.event_name != 'issues' && github.event_name != 'issue_comment' && !failure()
|
|
env:
|
|
REF_NAME: ${{ github.ref_name }}
|
|
steps:
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@v4
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Verify multi-arch manifests
|
|
# build-push-action with platforms: linux/amd64,linux/arm64 already pushes
|
|
# a combined manifest — this step confirms all images are reachable.
|
|
run: |
|
|
for image in base-apt base-node-deps base-pip-deps base-conan-deps base-android-sdk devcontainer; do
|
|
echo "Inspecting $REGISTRY/$IMAGE_NAME/$image:$REF_NAME"
|
|
docker manifest inspect "$REGISTRY/$IMAGE_NAME/$image:$REF_NAME" || \
|
|
echo "WARNING: manifest not found for $image (may not have been built on this run)"
|
|
done
|
|
|
|
# ============================================================================
|
|
# CodeQL Semantic Analysis (manual dispatch only)
|
|
# ============================================================================
|
|
|
|
codeql-analyze:
|
|
name: "CodeQL: ${{ matrix.language }}"
|
|
runs-on: ubuntu-latest
|
|
if: github.event_name == 'workflow_dispatch' && inputs.run_codeql == true
|
|
timeout-minutes: 360
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
language: ${{ inputs.codeql_languages == 'all' && fromJSON('["javascript-typescript","python","cpp","go"]') || fromJSON(format('["{0}"]', inputs.codeql_languages)) }}
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Initialize CodeQL
|
|
uses: github/codeql-action/init@v4
|
|
with:
|
|
languages: ${{ matrix.language }}
|
|
queries: security-and-quality
|
|
config-file: ./.github/codeql/codeql-config.yml
|
|
|
|
- name: Setup Node.js
|
|
if: matrix.language == 'javascript-typescript'
|
|
uses: actions/setup-node@v5
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'
|
|
|
|
- name: Setup Python
|
|
if: matrix.language == 'python'
|
|
uses: actions/setup-python@v5
|
|
with:
|
|
python-version: '3.11'
|
|
|
|
- name: Setup Go
|
|
if: matrix.language == 'go'
|
|
uses: actions/setup-go@v5
|
|
with:
|
|
go-version: '1.21'
|
|
|
|
- name: Autobuild
|
|
uses: github/codeql-action/autobuild@v4
|
|
|
|
- name: Perform CodeQL Analysis
|
|
uses: github/codeql-action/analyze@v4
|
|
with:
|
|
category: "/language:${{ matrix.language }}"
|
|
upload: true
|
|
wait-for-processing: true
|
|
|
|
codeql-summary:
|
|
name: "CodeQL: Summary"
|
|
needs: codeql-analyze
|
|
runs-on: ubuntu-latest
|
|
if: always() && github.event_name == 'workflow_dispatch' && inputs.run_codeql == true
|
|
steps:
|
|
- name: Summary Report
|
|
run: |
|
|
echo "## CodeQL Analysis Complete" >> $GITHUB_STEP_SUMMARY
|
|
echo "" >> $GITHUB_STEP_SUMMARY
|
|
echo "### Available Features" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Code Search**: Use GitHub Advanced Search with CodeQL queries" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **Security Tab**: View findings in repository Security tab" >> $GITHUB_STEP_SUMMARY
|
|
echo "- **API Access**: Query databases via CodeQL CLI or VS Code extension" >> $GITHUB_STEP_SUMMARY
|
|
|
|
# ============================================================================
|
|
# Summary Report
|
|
# ============================================================================
|
|
|
|
gates-summary:
|
|
name: "All Gates Summary"
|
|
runs-on: ubuntu-latest
|
|
needs: [gate-1-complete, gate-2-complete, gate-3-complete]
|
|
if: always()
|
|
steps:
|
|
- name: Download all gate artifacts
|
|
uses: actions/download-artifact@v6
|
|
with:
|
|
pattern: gate-*-complete-report
|
|
path: all-gate-artifacts/
|
|
merge-multiple: true
|
|
|
|
- name: Generate comprehensive summary
|
|
uses: actions/github-script@v7
|
|
with:
|
|
script: |
|
|
const gates = [
|
|
{ name: 'Gate 1: Code Quality', status: '${{ needs.gate-1-complete.result }}', steps: 7 },
|
|
{ name: 'Gate 2: Testing', status: '${{ needs.gate-2-complete.result }}', steps: 3 },
|
|
{ name: 'Gate 3: Build & Package', status: '${{ needs.gate-3-complete.result }}', steps: 2 }
|
|
];
|
|
|
|
let summary = '## Gated Pipeline Summary\n\n';
|
|
summary += '### Gate Results\n\n';
|
|
|
|
for (const gate of gates) {
|
|
const status = gate.status === 'success' ? 'passed' :
|
|
gate.status === 'failure' ? 'FAILED' :
|
|
gate.status === 'skipped' ? 'skipped' : 'pending';
|
|
summary += `**${gate.name}**: ${status} (${gate.steps} steps)\n`;
|
|
}
|
|
|
|
summary += '\n### Pipeline Flow\n\n';
|
|
summary += '```\n';
|
|
summary += 'Standalone: Triage (issues/PRs) | CodeQL (dispatch)\n';
|
|
summary += ' |\n';
|
|
summary += 'Gate 1: Code Quality (7 steps)\n';
|
|
summary += ' 1.1 DBAL Schemas 1.2 TypeScript 1.3 Lint\n';
|
|
summary += ' 1.4 Security 1.5 File Size 1.6 Complexity 1.7 Stubs\n';
|
|
summary += ' |\n';
|
|
summary += 'Gate 2: Testing (3 steps)\n';
|
|
summary += ' 2.1 Unit Tests (+ coverage) 2.2 E2E 2.3 DBAL\n';
|
|
summary += ' |\n';
|
|
summary += 'Gate 3: Build (2 steps)\n';
|
|
summary += ' 3.1 Build 3.2 Quality\n';
|
|
summary += ' |\n';
|
|
summary += 'Gate 4: Dev Feedback (PR only)\n';
|
|
summary += ' |\n';
|
|
summary += 'Gate 5: Staging (main push)\n';
|
|
summary += ' |\n';
|
|
summary += 'Gate 6: Production (release/manual)\n';
|
|
summary += ' |\n';
|
|
summary += 'Gate 7: Containers (push/tag/dispatch)\n';
|
|
summary += ' T1: base-apt, node-deps, pip-deps\n';
|
|
summary += ' T2: conan-deps, android-sdk\n';
|
|
summary += ' T3: devcontainer\n';
|
|
summary += ' Apps: 7 images -> Trivy scan -> Multi-arch manifests\n';
|
|
summary += '```\n\n';
|
|
|
|
console.log(summary);
|
|
|
|
if (context.eventName === 'pull_request') {
|
|
await github.rest.issues.createComment({
|
|
owner: context.repo.owner,
|
|
repo: context.repo.repo,
|
|
issue_number: context.issue.number,
|
|
body: summary
|
|
});
|
|
}
|
|
|
|
- name: Upload complete audit trail
|
|
uses: actions/upload-artifact@v6
|
|
with:
|
|
name: complete-gate-audit-trail
|
|
path: all-gate-artifacts/
|
|
retention-days: 30
|