Skip to content

Commit 87addf4

Browse files
authored
Merge pull request #7988 from KBVE/dev
Release: 1 feature, 2 fixes, 2 chores → Main
2 parents 9d34b0f + 9b017db commit 87addf4

30 files changed

+20921
-10252
lines changed

.github/workflows/ci-monday-e2e.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ jobs:
2626
name: Discover E2E Projects
2727
runs-on: ubuntu-latest
2828
timeout-minutes: 10
29+
env:
30+
NX_NO_CLOUD: true
2931
permissions:
3032
contents: read
3133
outputs:
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
name: CI - Weekly NX Report
2+
3+
on:
4+
schedule:
5+
- cron: '0 6 * * 2' # Tuesday 06:00 UTC
6+
workflow_dispatch:
7+
8+
concurrency:
9+
group: ${{ github.workflow }}
10+
cancel-in-progress: true
11+
12+
permissions: {}
13+
14+
jobs:
15+
# ── Fork & actor guard (mirrors utils-self-hosted-job) ───
16+
guard:
17+
name: Fork Guard
18+
runs-on: ubuntu-latest
19+
timeout-minutes: 2
20+
outputs:
21+
allowed: ${{ steps.check.outputs.allowed }}
22+
steps:
23+
- name: Verify origin and actor
24+
id: check
25+
uses: actions/github-script@v8
26+
with:
27+
script: |
28+
if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') {
29+
const headRepo = context.payload.pull_request?.head?.repo?.full_name;
30+
const baseRepo = context.payload.repository?.full_name;
31+
if (headRepo && headRepo !== baseRepo) {
32+
core.error(`Blocked: fork PR from ${headRepo} cannot use self-hosted runners`);
33+
core.setOutput('allowed', 'false');
34+
return;
35+
}
36+
}
37+
try {
38+
const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
39+
owner: context.repo.owner,
40+
repo: context.repo.repo,
41+
username: context.actor,
42+
});
43+
const level = data.permission;
44+
if (level === 'admin' || level === 'write') {
45+
core.info(`Actor ${context.actor} has ${level} access — allowed`);
46+
core.setOutput('allowed', 'true');
47+
} else {
48+
core.error(`Actor ${context.actor} has ${level} access — insufficient`);
49+
core.setOutput('allowed', 'false');
50+
}
51+
} catch (err) {
52+
core.error(`Permission check failed: ${err.message}`);
53+
core.setOutput('allowed', 'false');
54+
}
55+
56+
generate:
57+
name: Generate NX Report & Graph
58+
needs: guard
59+
if: needs.guard.outputs.allowed == 'true'
60+
runs-on: arc-runner-set
61+
timeout-minutes: 30
62+
permissions:
63+
contents: write
64+
pull-requests: write
65+
66+
steps:
67+
- name: Audit log
68+
run: |
69+
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)"
70+
71+
- name: Checkout dev branch
72+
uses: actions/checkout@v6
73+
with:
74+
ref: dev
75+
fetch-depth: 1
76+
77+
- name: Setup Node v24
78+
uses: actions/setup-node@v6
79+
with:
80+
node-version: 24
81+
82+
- name: Setup pnpm v10
83+
uses: pnpm/action-setup@v4
84+
with:
85+
version: 10
86+
run_install: false
87+
88+
- name: Get pnpm Store Path
89+
id: pnpm-store
90+
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
91+
92+
- name: Setup pnpm Cache
93+
uses: actions/cache@v5
94+
with:
95+
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
96+
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml', 'package.json') }}
97+
restore-keys: |
98+
${{ runner.os }}-pnpm-store-
99+
100+
- name: Install pnpm Dependencies
101+
run: pnpm install
102+
103+
- name: Setup Nx Cache
104+
uses: actions/cache@v5
105+
with:
106+
path: .nx/cache
107+
key: ${{ runner.os }}-nx-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }}
108+
restore-keys: |
109+
${{ runner.os }}-nx-${{ hashFiles('**/pnpm-lock.yaml') }}-
110+
${{ runner.os }}-nx-
111+
112+
# ── Generate NX data ────────────────────────────────
113+
- name: Generate NX Report
114+
id: nx-report
115+
run: |
116+
set -euo pipefail
117+
pnpm nx report > /tmp/nx-report-raw.txt 2>&1 || true
118+
echo "report_date=$(date -u +%Y-%m-%d)" >> "$GITHUB_OUTPUT"
119+
echo "report_timestamp=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"
120+
121+
- name: Validate NX Report output
122+
run: |
123+
set -euo pipefail
124+
if [ ! -s /tmp/nx-report-raw.txt ]; then
125+
echo "::error::NX report output is empty"
126+
exit 1
127+
fi
128+
if ! grep -q "^nx " /tmp/nx-report-raw.txt; then
129+
echo "::warning::NX report may be malformed — missing nx version line"
130+
cat /tmp/nx-report-raw.txt
131+
fi
132+
echo "NX report validated ($(wc -l < /tmp/nx-report-raw.txt) lines)"
133+
134+
- name: Generate NX Graph JSON
135+
run: |
136+
set -euo pipefail
137+
pnpm nx graph --file=/tmp/nx-graph.json --open=false
138+
rm -rf static 2>/dev/null || true
139+
140+
- name: Validate NX Graph JSON
141+
run: |
142+
set -euo pipefail
143+
if [ ! -s /tmp/nx-graph.json ]; then
144+
echo "::error::NX graph JSON is empty"
145+
exit 1
146+
fi
147+
NODE_COUNT=$(python3 -c "import json; d=json.load(open('/tmp/nx-graph.json')); print(len(d['graph']['nodes']))")
148+
if [ "$NODE_COUNT" -eq 0 ]; then
149+
echo "::error::NX graph has zero nodes"
150+
exit 1
151+
fi
152+
echo "NX graph validated ($NODE_COUNT nodes)"
153+
154+
# ── Build NX Report MDX ─────────────────────────────
155+
- name: Build NX Report MDX
156+
shell: bash
157+
run: |
158+
set -euo pipefail
159+
REPORT_DATE="${{ steps.nx-report.outputs.report_date }}"
160+
REPORT_TIMESTAMP="${{ steps.nx-report.outputs.report_timestamp }}"
161+
DEST="apps/kbve/astro-kbve/src/content/docs/dashboard/nx-report.mdx"
162+
mkdir -p "$(dirname "$DEST")"
163+
164+
NODE_VER=$(grep "^Node" /tmp/nx-report-raw.txt | awk -F: '{print $2}' | xargs)
165+
OS_INFO=$(grep "^OS" /tmp/nx-report-raw.txt | awk -F: '{print $2}' | xargs)
166+
NX_VER=$(grep "^nx " /tmp/nx-report-raw.txt | awk '{print $NF}' | xargs)
167+
PNPM_VER=$(grep "^pnpm" /tmp/nx-report-raw.txt | awk -F: '{print $2}' | xargs)
168+
169+
{
170+
printf '%s\n' '---'
171+
printf '%s\n' 'title: NX Workspace Report'
172+
printf '%s\n' 'description: |'
173+
printf '%s\n' ' Weekly auto-generated NX workspace report for the KBVE monorepo.'
174+
printf '%s\n' 'sidebar:'
175+
printf '%s\n' ' label: NX Report'
176+
printf '%s\n' ' order: 100'
177+
printf '%s\n' 'editUrl: false'
178+
printf '%s\n' '---'
179+
printf '\n'
180+
printf '%s\n' "## NX Workspace Report"
181+
printf '\n'
182+
printf '%s\n' "> Last generated: **${REPORT_TIMESTAMP}**"
183+
printf '%s\n' ">"
184+
printf '%s\n' "> Auto-generated every Tuesday by the \`ci-weekly-nx-report\` workflow."
185+
printf '\n'
186+
printf '%s\n' "### Environment"
187+
printf '\n'
188+
printf '%s\n' "| Component | Version |"
189+
printf '%s\n' "|-----------|---------|"
190+
printf '%s\n' "| Node | ${NODE_VER} |"
191+
printf '%s\n' "| OS | ${OS_INFO} |"
192+
printf '%s\n' "| pnpm | ${PNPM_VER} |"
193+
printf '%s\n' "| Nx | ${NX_VER} |"
194+
printf '\n'
195+
printf '%s\n' "### Full Report"
196+
printf '\n'
197+
printf '%s\n' '```'
198+
cat /tmp/nx-report-raw.txt
199+
printf '%s\n' '```'
200+
printf '\n'
201+
printf '%s\n' '---'
202+
printf '\n'
203+
printf '%s\n' '*Auto-generated by [ci-weekly-nx-report.yml](https://github.com/KBVE/kbve/actions/workflows/ci-weekly-nx-report.yml)*'
204+
} > "$DEST"
205+
206+
# ── Build NX Graph MDX ──────────────────────────────
207+
- name: Build NX Graph MDX
208+
run: |
209+
set -euo pipefail
210+
DEST="apps/kbve/astro-kbve/src/content/docs/dashboard/nx-graph.mdx"
211+
TIMESTAMP="${{ steps.nx-report.outputs.report_timestamp }}"
212+
python3 scripts/nx-graph-to-mdx.py /tmp/nx-graph.json "$DEST" "$TIMESTAMP"
213+
214+
# ── Validate generated MDX ──────────────────────────
215+
- name: Validate generated MDX files
216+
run: |
217+
set -euo pipefail
218+
REPORT="apps/kbve/astro-kbve/src/content/docs/dashboard/nx-report.mdx"
219+
GRAPH="apps/kbve/astro-kbve/src/content/docs/dashboard/nx-graph.mdx"
220+
221+
for f in "$REPORT" "$GRAPH"; do
222+
if [ ! -s "$f" ]; then
223+
echo "::error::Generated file is empty: $f"
224+
exit 1
225+
fi
226+
if ! head -1 "$f" | grep -q '^---$'; then
227+
echo "::error::Missing YAML frontmatter in $f"
228+
exit 1
229+
fi
230+
echo "OK: $f ($(wc -c < "$f" | xargs) bytes)"
231+
done
232+
233+
# Sanity: graph MDX should contain mermaid + Starlight imports
234+
if ! grep -q 'CardGrid' "$GRAPH"; then
235+
echo "::warning::Graph MDX missing Starlight CardGrid component"
236+
fi
237+
if ! grep -q 'mermaid' "$GRAPH"; then
238+
echo "::warning::Graph MDX missing mermaid diagram"
239+
fi
240+
241+
# ── Commit & PR ──────────────────────────────────────
242+
- name: Commit and push
243+
id: commit
244+
env:
245+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
246+
run: |
247+
set -euo pipefail
248+
git config user.name "github-actions[bot]"
249+
git config user.email "github-actions[bot]@users.noreply.github.com"
250+
251+
BRANCH_NAME="chore/nx-report-$(date +%Y%m%d)"
252+
REPORT_DATE="${{ steps.nx-report.outputs.report_date }}"
253+
254+
git add \
255+
apps/kbve/astro-kbve/src/content/docs/dashboard/nx-report.mdx \
256+
apps/kbve/astro-kbve/src/content/docs/dashboard/nx-graph.mdx
257+
258+
if git diff --cached --quiet; then
259+
echo "No changes to commit"
260+
echo "pushed=false" >> "$GITHUB_OUTPUT"
261+
exit 0
262+
fi
263+
264+
# Guard: if branch already exists on remote, skip to avoid conflicts
265+
if git ls-remote --exit-code --heads origin "$BRANCH_NAME" > /dev/null 2>&1; then
266+
echo "::warning::Branch $BRANCH_NAME already exists on remote — skipping push"
267+
echo "pushed=false" >> "$GITHUB_OUTPUT"
268+
exit 0
269+
fi
270+
271+
git checkout -b "$BRANCH_NAME"
272+
git commit -m "chore(astro-kbve): weekly nx report and graph ${REPORT_DATE}"
273+
git push -u origin "$BRANCH_NAME"
274+
275+
echo "pushed=true" >> "$GITHUB_OUTPUT"
276+
echo "branch_name=$BRANCH_NAME" >> "$GITHUB_OUTPUT"
277+
278+
- name: Create PR
279+
if: steps.commit.outputs.pushed == 'true'
280+
env:
281+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
282+
run: |
283+
set -euo pipefail
284+
BRANCH_NAME="${{ steps.commit.outputs.branch_name }}"
285+
REPORT_DATE="${{ steps.nx-report.outputs.report_date }}"
286+
287+
EXISTING=$(gh pr list --base dev --head "$BRANCH_NAME" --state open --json number -q '.[0].number' || echo "")
288+
if [ -n "$EXISTING" ]; then
289+
echo "PR #$EXISTING already exists for $BRANCH_NAME"
290+
exit 0
291+
fi
292+
293+
cat > /tmp/pr-body.md << 'PRBODY'
294+
## Summary
295+
- Weekly auto-generated NX workspace report (versions, plugins, cache usage)
296+
- Weekly NX dependency graph with mermaid diagram and project index
297+
298+
## Content
299+
- dashboard/nx-report.mdx: Parsed NX report with environment table
300+
- dashboard/nx-graph.mdx: Dependency graph with mermaid diagram, project index, and per-project details
301+
302+
## Test Plan
303+
- Verify NX report page renders correctly in Starlight
304+
- Verify mermaid dependency diagram renders in NX graph page
305+
- Check project index table has correct dependency counts
306+
307+
---
308+
*Auto-generated by ci-weekly-nx-report.yml*
309+
PRBODY
310+
311+
sed 's/^ //' /tmp/pr-body.md > /tmp/pr-body-clean.md
312+
313+
gh pr create \
314+
--base dev \
315+
--head "$BRANCH_NAME" \
316+
--title "chore(astro-kbve): weekly nx report ${REPORT_DATE}" \
317+
--body-file /tmp/pr-body-clean.md
318+
319+
# ── Cleanup ──────────────────────────────────────────
320+
- name: Cleanup workspace
321+
if: always()
322+
run: |
323+
echo "Cleaning up workspace..."
324+
rm -f /tmp/nx-report-raw.txt /tmp/nx-graph.json /tmp/pr-body.md /tmp/pr-body-clean.md
325+
rm -rf "$GITHUB_WORKSPACE"/* "$GITHUB_WORKSPACE"/.[!.]* 2>/dev/null || true

.github/workflows/docker-test-app.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ jobs:
2727
name: Test ${{ inputs.project }} Docker App
2828
runs-on: ${{ inputs.runner }}
2929
timeout-minutes: 60
30+
env:
31+
NX_NO_CLOUD: true
3032
permissions:
3133
contents: read
3234
packages: write

.github/workflows/npm-test-package.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ jobs:
1111
test:
1212
name: Test ${{ inputs.project }} NPM Package
1313
runs-on: ubuntu-latest
14+
env:
15+
NX_NO_CLOUD: true
1416
permissions:
1517
contents: read
1618

.github/workflows/python-test-package.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ jobs:
1111
test:
1212
name: Test ${{ inputs.project }} Python Package
1313
runs-on: ubuntu-latest
14+
env:
15+
NX_NO_CLOUD: true
1416
permissions:
1517
contents: read
1618

.github/workflows/rust-test-crate.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ on:
1010
jobs:
1111
test:
1212
runs-on: ubuntu-latest
13+
env:
14+
NX_NO_CLOUD: true
1315
permissions:
1416
contents: read
1517

.github/workflows/utils-astro-deployment.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ jobs:
4343
deploy:
4444
runs-on: ubuntu-latest
4545
timeout-minutes: 30
46+
env:
47+
NX_NO_CLOUD: true
4648
permissions:
4749
contents: read
4850
steps:

.github/workflows/utils-ci-lint-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ jobs:
2222
name: 'Lint and Test'
2323
runs-on: ubuntu-latest
2424
timeout-minutes: 120
25+
env:
26+
NX_NO_CLOUD: true
2527
permissions:
2628
contents: read
2729
steps:

.github/workflows/utils-npm-publish.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ jobs:
1515
name: Publish ${{ inputs.project }} NPM Package
1616
runs-on: ubuntu-latest
1717
timeout-minutes: 20
18+
env:
19+
NX_NO_CLOUD: true
1820
permissions:
1921
contents: read
2022
id-token: write

0 commit comments

Comments
 (0)