Skip to content
Merged
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
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci-monday-e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ jobs:
name: Discover E2E Projects
runs-on: ubuntu-latest
timeout-minutes: 10
env:
NX_NO_CLOUD: true
permissions:
contents: read
outputs:
Expand Down
325 changes: 325 additions & 0 deletions .github/workflows/ci-weekly-nx-report.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,325 @@
name: CI - Weekly NX Report

on:
schedule:
- cron: '0 6 * * 2' # Tuesday 06:00 UTC
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true

permissions: {}

jobs:
# ── Fork & actor guard (mirrors utils-self-hosted-job) ───
guard:
name: Fork Guard
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
allowed: ${{ steps.check.outputs.allowed }}
steps:
- name: Verify origin and actor
id: check
uses: actions/github-script@v8
with:
script: |
if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
const headRepo = context.payload.pull_request?.head?.repo?.full_name;
const baseRepo = context.payload.repository?.full_name;
if (headRepo && headRepo !== baseRepo) {
core.error(`Blocked: fork PR from ${headRepo} cannot use self-hosted runners`);
core.setOutput('allowed', 'false');
return;
}
}
try {
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: context.actor,
});
const level = data.permission;
if (level === 'admin' || level === 'write') {
core.info(`Actor ${context.actor} has ${level} access — allowed`);
core.setOutput('allowed', 'true');
} else {
core.error(`Actor ${context.actor} has ${level} access — insufficient`);
core.setOutput('allowed', 'false');
}
} catch (err) {
core.error(`Permission check failed: ${err.message}`);
core.setOutput('allowed', 'false');
}

generate:
name: Generate NX Report & Graph
needs: guard
if: needs.guard.outputs.allowed == 'true'
runs-on: arc-runner-set
timeout-minutes: 30
permissions:
contents: write
pull-requests: write

steps:
- name: Audit log
run: |
echo "::notice::NX report job: actor=${{ github.actor }}, event=${{ github.event_name }}, ref=${{ github.ref }}, run_id=${{ github.run_id }}, time=$(date -u +%Y-%m-%dT%H:%M:%SZ)"

- name: Checkout dev branch
uses: actions/checkout@v6
with:
ref: dev
fetch-depth: 1

- name: Setup Node v24
uses: actions/setup-node@v6
with:
node-version: 24

- name: Setup pnpm v10
uses: pnpm/action-setup@v4
with:
version: 10
run_install: false

- name: Get pnpm Store Path
id: pnpm-store
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT

- name: Setup pnpm Cache
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml', 'package.json') }}
restore-keys: |
${{ runner.os }}-pnpm-store-

- name: Install pnpm Dependencies
run: pnpm install

- name: Setup Nx Cache
uses: actions/cache@v5
with:
path: .nx/cache
key: ${{ runner.os }}-nx-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-nx-${{ hashFiles('**/pnpm-lock.yaml') }}-
${{ runner.os }}-nx-

# ── Generate NX data ────────────────────────────────
- name: Generate NX Report
id: nx-report
run: |
set -euo pipefail
pnpm nx report > /tmp/nx-report-raw.txt 2>&1 || true
echo "report_date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
echo "report_timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"

- name: Validate NX Report output
run: |
set -euo pipefail
if [ ! -s /tmp/nx-report-raw.txt ]; then
echo "::error::NX report output is empty"
exit 1
fi
if ! grep -q "^nx " /tmp/nx-report-raw.txt; then
echo "::warning::NX report may be malformed — missing nx version line"
cat /tmp/nx-report-raw.txt
fi
echo "NX report validated ($(wc -l < /tmp/nx-report-raw.txt) lines)"

- name: Generate NX Graph JSON
run: |
set -euo pipefail
pnpm nx graph --file=/tmp/nx-graph.json --open=false
rm -rf static 2>/dev/null || true

- name: Validate NX Graph JSON
run: |
set -euo pipefail
if [ ! -s /tmp/nx-graph.json ]; then
echo "::error::NX graph JSON is empty"
exit 1
fi
NODE_COUNT=$(python3 -c "import json; d=json.load(open('/tmp/nx-graph.json')); print(len(d['graph']['nodes']))")
if [ "$NODE_COUNT" -eq 0 ]; then
echo "::error::NX graph has zero nodes"
exit 1
fi
echo "NX graph validated ($NODE_COUNT nodes)"

# ── Build NX Report MDX ─────────────────────────────
- name: Build NX Report MDX
shell: bash
run: |
set -euo pipefail
REPORT_DATE="${{ steps.nx-report.outputs.report_date }}"
REPORT_TIMESTAMP="${{ steps.nx-report.outputs.report_timestamp }}"
DEST="apps/kbve/astro-kbve/src/content/docs/dashboard/nx-report.mdx"
mkdir -p "$(dirname "$DEST")"

NODE_VER=$(grep "^Node" /tmp/nx-report-raw.txt | awk -F: '{print $2}' | xargs)
OS_INFO=$(grep "^OS" /tmp/nx-report-raw.txt | awk -F: '{print $2}' | xargs)
NX_VER=$(grep "^nx " /tmp/nx-report-raw.txt | awk '{print $NF}' | xargs)
PNPM_VER=$(grep "^pnpm" /tmp/nx-report-raw.txt | awk -F: '{print $2}' | xargs)

{
printf '%s\n' '---'
printf '%s\n' 'title: NX Workspace Report'
printf '%s\n' 'description: |'
printf '%s\n' ' Weekly auto-generated NX workspace report for the KBVE monorepo.'
printf '%s\n' 'sidebar:'
printf '%s\n' ' label: NX Report'
printf '%s\n' ' order: 100'
printf '%s\n' 'editUrl: false'
printf '%s\n' '---'
printf '\n'
printf '%s\n' "## NX Workspace Report"
printf '\n'
printf '%s\n' "> Last generated: **${REPORT_TIMESTAMP}**"
printf '%s\n' ">"
printf '%s\n' "> Auto-generated every Tuesday by the \`ci-weekly-nx-report\` workflow."
printf '\n'
printf '%s\n' "### Environment"
printf '\n'
printf '%s\n' "| Component | Version |"
printf '%s\n' "|-----------|---------|"
printf '%s\n' "| Node | ${NODE_VER} |"
printf '%s\n' "| OS | ${OS_INFO} |"
printf '%s\n' "| pnpm | ${PNPM_VER} |"
printf '%s\n' "| Nx | ${NX_VER} |"
printf '\n'
printf '%s\n' "### Full Report"
printf '\n'
printf '%s\n' '```'
cat /tmp/nx-report-raw.txt
printf '%s\n' '```'
printf '\n'
printf '%s\n' '---'
printf '\n'
printf '%s\n' '*Auto-generated by [ci-weekly-nx-report.yml](https://github.com/KBVE/kbve/actions/workflows/ci-weekly-nx-report.yml)*'
} > "$DEST"

# ── Build NX Graph MDX ──────────────────────────────
- name: Build NX Graph MDX
run: |
set -euo pipefail
DEST="apps/kbve/astro-kbve/src/content/docs/dashboard/nx-graph.mdx"
TIMESTAMP="${{ steps.nx-report.outputs.report_timestamp }}"
python3 scripts/nx-graph-to-mdx.py /tmp/nx-graph.json "$DEST" "$TIMESTAMP"

# ── Validate generated MDX ──────────────────────────
- name: Validate generated MDX files
run: |
set -euo pipefail
REPORT="apps/kbve/astro-kbve/src/content/docs/dashboard/nx-report.mdx"
GRAPH="apps/kbve/astro-kbve/src/content/docs/dashboard/nx-graph.mdx"

for f in "$REPORT" "$GRAPH"; do
if [ ! -s "$f" ]; then
echo "::error::Generated file is empty: $f"
exit 1
fi
if ! head -1 "$f" | grep -q '^---$'; then
echo "::error::Missing YAML frontmatter in $f"
exit 1
fi
echo "OK: $f ($(wc -c < "$f" | xargs) bytes)"
done

# Sanity: graph MDX should contain mermaid + Starlight imports
if ! grep -q 'CardGrid' "$GRAPH"; then
echo "::warning::Graph MDX missing Starlight CardGrid component"
fi
if ! grep -q 'mermaid' "$GRAPH"; then
echo "::warning::Graph MDX missing mermaid diagram"
fi

# ── Commit & PR ──────────────────────────────────────
- name: Commit and push
id: commit
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

BRANCH_NAME="chore/nx-report-$(date +%Y%m%d)"
REPORT_DATE="${{ steps.nx-report.outputs.report_date }}"

git add \
apps/kbve/astro-kbve/src/content/docs/dashboard/nx-report.mdx \
apps/kbve/astro-kbve/src/content/docs/dashboard/nx-graph.mdx

if git diff --cached --quiet; then
echo "No changes to commit"
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi

# Guard: if branch already exists on remote, skip to avoid conflicts
if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null 2>&1; then
echo "::warning::Branch $BRANCH_NAME already exists on remote — skipping push"
echo "pushed=false" >> "$GITHUB_OUTPUT"
exit 0
fi

git checkout -b "$BRANCH_NAME"
git commit -m "chore(astro-kbve): weekly nx report and graph ${REPORT_DATE}"
git push -u origin "$BRANCH_NAME"

echo "pushed=true" >> "$GITHUB_OUTPUT"
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"

- name: Create PR
if: steps.commit.outputs.pushed == 'true'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
BRANCH_NAME="${{ steps.commit.outputs.branch_name }}"
REPORT_DATE="${{ steps.nx-report.outputs.report_date }}"

EXISTING=$(gh pr list --base dev --head "$BRANCH_NAME" --state open --json number -q '.[0].number' || echo "")
if [ -n "$EXISTING" ]; then
echo "PR #$EXISTING already exists for $BRANCH_NAME"
exit 0
fi

cat > /tmp/pr-body.md << 'PRBODY'
## Summary
- Weekly auto-generated NX workspace report (versions, plugins, cache usage)
- Weekly NX dependency graph with mermaid diagram and project index

## Content
- dashboard/nx-report.mdx: Parsed NX report with environment table
- dashboard/nx-graph.mdx: Dependency graph with mermaid diagram, project index, and per-project details

## Test Plan
- Verify NX report page renders correctly in Starlight
- Verify mermaid dependency diagram renders in NX graph page
- Check project index table has correct dependency counts

---
*Auto-generated by ci-weekly-nx-report.yml*
PRBODY

sed 's/^ //' /tmp/pr-body.md > /tmp/pr-body-clean.md

gh pr create \
--base dev \
--head "$BRANCH_NAME" \
--title "chore(astro-kbve): weekly nx report ${REPORT_DATE}" \
--body-file /tmp/pr-body-clean.md

# ── Cleanup ──────────────────────────────────────────
- name: Cleanup workspace
if: always()
run: |
echo "Cleaning up workspace..."
rm -f /tmp/nx-report-raw.txt /tmp/nx-graph.json /tmp/pr-body.md /tmp/pr-body-clean.md
rm -rf "$GITHUB_WORKSPACE"/* "$GITHUB_WORKSPACE"/.[!.]* 2>/dev/null || true
2 changes: 2 additions & 0 deletions .github/workflows/docker-test-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ jobs:
name: Test ${{ inputs.project }} Docker App
runs-on: ${{ inputs.runner }}
timeout-minutes: 60
env:
NX_NO_CLOUD: true
permissions:
contents: read
packages: write
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/npm-test-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
test:
name: Test ${{ inputs.project }} NPM Package
runs-on: ubuntu-latest
env:
NX_NO_CLOUD: true
permissions:
contents: read

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/python-test-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ jobs:
test:
name: Test ${{ inputs.project }} Python Package
runs-on: ubuntu-latest
env:
NX_NO_CLOUD: true
permissions:
contents: read

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/rust-test-crate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ on:
jobs:
test:
runs-on: ubuntu-latest
env:
NX_NO_CLOUD: true
permissions:
contents: read

Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/utils-astro-deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ jobs:
deploy:
runs-on: ubuntu-latest
timeout-minutes: 30
env:
NX_NO_CLOUD: true
permissions:
contents: read
steps:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/utils-ci-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:
name: 'Lint and Test'
runs-on: ubuntu-latest
timeout-minutes: 120
env:
NX_NO_CLOUD: true
permissions:
contents: read
steps:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/utils-npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jobs:
name: Publish ${{ inputs.project }} NPM Package
runs-on: ubuntu-latest
timeout-minutes: 20
env:
NX_NO_CLOUD: true
permissions:
contents: read
id-token: write
Expand Down
Loading
Loading