-
Notifications
You must be signed in to change notification settings - Fork 0
688 lines (588 loc) · 25.5 KB
/
pr-validation.yml
File metadata and controls
688 lines (588 loc) · 25.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
name: PR Validation
on:
pull_request:
branches: [ "main" ]
types: [opened, synchronize, reopened, ready_for_review]
# Ensure only one workflow runs per PR
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
jobs:
# Validate PR title follows Conventional Commits
validate-pr-title:
name: Validate PR Title
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false && github.event.pull_request.user.login != 'dependabot[bot]'
steps:
- name: Validate PR title
uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
types: |
feat
fix
docs
style
refactor
perf
test
build
ci
chore
revert
requireScope: false
subjectPattern: ^[A-Z].+$
subjectPatternError: |
The subject "{subject}" found in the pull request title "{title}"
didn't match the configured pattern. Please ensure that the subject
starts with an uppercase character.
# Validate PR has description
validate-pr-description:
name: Validate PR Description
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false && github.event.pull_request.user.login != 'dependabot[bot]'
steps:
- name: Check PR description
uses: actions/github-script@v8
with:
script: |
const prBody = context.payload.pull_request.body || '';
const minLength = 50;
if (prBody.trim().length < minLength) {
core.setFailed(
`PR description is too short (${prBody.trim().length} chars). ` +
`Please provide a meaningful description (minimum ${minLength} chars).`
);
return;
}
// Check for required sections (flexible check)
const hasWhat = /###?\s*What/i.test(prBody);
const hasWhy = /###?\s*Why/i.test(prBody);
const hasTesting = /###?\s*Testing/i.test(prBody) || /\[x\].*test/i.test(prBody);
if (!hasWhat || !hasWhy) {
core.setFailed(
'PR description is missing required sections. ' +
'Please use the PR template and fill in: What, Why, and Testing sections.'
);
return;
}
core.info('✅ PR description looks good!');
# Check PR size
check-pr-size:
name: Check PR Size
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- name: Check PR size
uses: actions/github-script@v8
with:
script: |
const pr = context.payload.pull_request;
const additions = pr.additions || 0;
const deletions = pr.deletions || 0;
const totalChanges = additions + deletions;
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
per_page: 100,
});
const isMaintenancePath = (path) => {
return (
path.startsWith('.squad/') ||
path.startsWith('docs/') ||
path.startsWith('.github/instructions/') ||
/(^|\/)README(\..+)?$/i.test(path) ||
/(^|\/)CHANGELOG(\..+)?$/i.test(path) ||
/(^|\/)CONTRIBUTING(\..+)?$/i.test(path) ||
/(^|\/)SECURITY(\..+)?$/i.test(path) ||
/(^|\/)LICENSE(\..+)?$/i.test(path) ||
/(^|\/)BENCHMARK_PR_STATUS(\..+)?$/i.test(path) ||
path.endsWith('.md')
);
};
const maintenanceOnly = files.length > 0 && files.every((f) => isMaintenancePath(f.filename));
// Soft limit (warning) and hard limit (failure)
const hardLimit = 1500;
const softLimit = 400;
if (totalChanges > hardLimit) {
if (maintenanceOnly) {
core.warning(
`⚠️ Large maintenance-only PR (${totalChanges} lines changed). ` +
`Allowing because all changed files are docs/.squad/instructions metadata.`
);
return;
}
core.setFailed(
`⚠️ PR is too large (${totalChanges} lines changed). ` +
`Please consider breaking it into smaller PRs (< ${hardLimit} lines).`
);
return;
}
if (totalChanges > softLimit) {
core.warning(
`⚠️ PR is getting large (${totalChanges} lines changed). ` +
`Consider breaking it into smaller PRs for easier review.`
);
} else {
core.info(`✅ PR size is good (${totalChanges} lines changed)`);
}
# Lint PR changes
lint-check:
name: Lint Check
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
# Pin the SDK to the version in global.json so that `dotnet format`
# produces identical output regardless of which SDK the runner ships.
# Without this, a newer SDK (e.g. 10.0.201 vs 10.0.103) may apply
# different formatting rules, causing false-positive lint failures.
env:
DOTNET_ROLL_FORWARD: LatestPatch
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup .NET (from global.json)
uses: actions/setup-dotnet@v4
with:
global-json-file: global.json
- name: Install WASM workloads
run: dotnet workload install wasm-experimental wasm-tools
- name: Restore
run: dotnet restore
env:
# Suppress NuGet audit during lint restore — audit is not the lint
# check's concern; the security-scan job handles vulnerability detection.
DOTNET_NUGET_AUDIT: "false"
- name: Get changed C# files
id: changed-files
run: |
# Get list of changed .cs files in the PR
CHANGED_FILES=$(git diff --name-only --diff-filter=ACMRT origin/${{ github.event.pull_request.base.ref }}...HEAD | grep '\.cs$' || true)
if [ -z "$CHANGED_FILES" ]; then
echo "has_cs_files=false" >> $GITHUB_OUTPUT
echo "ℹ️ No C# files changed in this PR"
else
echo "has_cs_files=true" >> $GITHUB_OUTPUT
# Convert newlines to spaces and store
FILES_SPACE_SEPARATED=$(echo "$CHANGED_FILES" | tr '\n' ' ')
echo "files=$FILES_SPACE_SEPARATED" >> $GITHUB_OUTPUT
echo "📝 Changed C# files:"
echo "$CHANGED_FILES"
fi
- name: Format check changed files
if: steps.changed-files.outputs.has_cs_files == 'true'
run: |
# Check formatting only for changed files
FILES="${{ steps.changed-files.outputs.files }}"
echo "🔍 Checking formatting for changed files..."
echo "Files: $FILES"
# Run format check with --include for each file.
# --exclude-diagnostics IDE1006: Exclude naming-rule violations
# (e.g. "Missing prefix: '_'") because dotnet format can detect but
# NOT auto-fix them (renaming is a refactoring, not formatting).
# Without this flag, --verify-no-changes reports false positives for
# files that would be "Formatted" yet produce zero diff.
dotnet format --verify-no-changes --include $FILES --verbosity diagnostic \
--exclude-diagnostics IDE1006
if [ $? -ne 0 ]; then
echo ""
echo "❌ Code formatting issues detected in your changes."
echo "Please run the following command locally:"
echo " dotnet format --include $FILES"
echo ""
exit 1
fi
echo "✅ Code formatting is correct for all changed files"
- name: Skip format check
if: steps.changed-files.outputs.has_cs_files == 'false'
run: |
echo "✅ No C# files to check - skipping format validation"
# Security scan
security-scan:
name: Security Scan
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Install gitleaks CLI
run: |
GITLEAKS_VERSION="8.28.0"
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" -o /tmp/gitleaks.tar.gz
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
/tmp/gitleaks version
- name: Secrets scan (gitleaks)
run: /tmp/gitleaks git --redact --config .gitleaks.toml
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install WASM workloads
run: dotnet workload install wasm-experimental wasm-tools
- name: Restore
run: dotnet restore
env:
# Suppress audit during restore — we run our own scan below
DOTNET_NUGET_AUDIT: "false"
- name: Check for vulnerable direct packages
run: |
echo "🔍 Scanning direct packages for vulnerabilities..."
dotnet list package --vulnerable 2>&1 | tee direct-vulnerability-report.txt
if grep -qi "critical\|high" direct-vulnerability-report.txt; then
echo "❌ Critical or High severity vulnerabilities detected in direct dependencies!"
echo "Please update the affected packages before merging."
exit 1
else
echo "✅ No critical or high severity vulnerabilities in direct dependencies"
fi
- name: Check for vulnerable transitive packages
run: |
echo "🔍 Scanning transitive packages for vulnerabilities..."
dotnet list package --vulnerable --include-transitive 2>&1 | tee transitive-vulnerability-report.txt
if grep -qi "critical\|high" transitive-vulnerability-report.txt; then
echo "❌ Critical or High severity vulnerabilities detected in transitive dependencies!"
echo "These usually require updating one or more direct package versions."
echo "Review the report above for details."
# Allow temporary exceptions via ALLOW_TRANSITIVE_VULNS env var.
# Set it to a tracking issue URL to document the exception.
if [ -n "${ALLOW_TRANSITIVE_VULNS:-}" ]; then
if echo "${ALLOW_TRANSITIVE_VULNS}" | grep -Eq '^https?://'; then
echo "⚠️ Temporarily allowing transitive vulnerabilities due to tracked issue:"
echo " ${ALLOW_TRANSITIVE_VULNS}"
echo "Ensure this exception is revisited and removed before the next release."
else
echo "❌ ALLOW_TRANSITIVE_VULNS is set but is not a valid http(s) URL:"
echo " ${ALLOW_TRANSITIVE_VULNS}"
echo "Please set it to a link to the tracking issue or security exception document."
exit 1
fi
else
echo "❌ Failing build because high/critical transitive vulnerabilities were found."
echo "To temporarily allow this (with explicit tracking), set ALLOW_TRANSITIVE_VULNS"
echo "to the URL of the issue or security exception that documents the risk."
exit 1
fi
else
echo "✅ No critical or high severity vulnerabilities in transitive dependencies"
fi
# Template smoke test — scaffold and build each dotnet new template
template-smoke-test:
name: Template Smoke Test
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Nerdbank.GitVersioning requires full git history to compute versions
fetch-depth: 0
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install WASM workloads
run: dotnet workload install wasm-experimental wasm-tools
- name: Pack framework packages to local feed
run: |
LOCAL_FEED=$(pwd)/local-packages
mkdir -p "$LOCAL_FEED"
# Use a version that matches the template's 1.0.0-* floating constraint
PACK_VERSION="1.0.0-ci"
# Pack framework projects in dependency order.
# -p:Version overrides Nerdbank.GitVersioning so ProjectReference
# dependencies also resolve as 1.0.0-ci in the local feed.
for project in \
Picea.Abies/Picea.Abies.csproj \
Picea.Abies.Browser/Picea.Abies.Browser.csproj \
Picea.Abies.Server/Picea.Abies.Server.csproj \
Picea.Abies.Server.Kestrel/Picea.Abies.Server.Kestrel.csproj; do
echo "📦 Packing $project as $PACK_VERSION..."
dotnet pack "$project" -c Release -o "$LOCAL_FEED" \
-p:Version="$PACK_VERSION" \
-p:PackageVersion="$PACK_VERSION"
done
echo ""
echo "📦 Local packages:"
ls -la "$LOCAL_FEED"
env:
DOTNET_NUGET_AUDIT: "false"
- name: Create NuGet config for template builds
run: |
# Create a nuget.config that prioritises the local feed for framework
# packages but falls back to nuget.org for transitive dependencies
# (e.g. Picea, Praefixum, Microsoft.AspNetCore.App).
cat > /tmp/template-nuget.config <<EOF
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="local" value="$(pwd)/local-packages" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
EOF
echo "📄 NuGet config:"
cat /tmp/template-nuget.config
- name: Install template pack from source
run: |
dotnet new install Picea.Abies.Templates/
echo ""
echo "📋 Installed templates:"
dotnet new list abies
- name: Detect template-related changes
id: template-changes
run: |
CHANGED=$(git diff --name-only --diff-filter=ACMRT origin/${{ github.event.pull_request.base.ref }}...HEAD | grep '^Picea\.Abies\.Templates/' || true)
if [ -n "$CHANGED" ]; then
echo "has_template_changes=true" >> $GITHUB_OUTPUT
echo "Detected template changes:"
echo "$CHANGED"
else
echo "has_template_changes=false" >> $GITHUB_OUTPUT
echo "No template source changes detected"
fi
- name: Smoke test — abies-browser
run: |
TEMP_DIR=$(mktemp -d)
echo "🧪 Scaffolding abies-browser into $TEMP_DIR..."
dotnet new abies-browser -o "$TEMP_DIR"
echo "🔨 Restoring scaffolded project..."
dotnet restore "$TEMP_DIR" --configfile /tmp/template-nuget.config
echo "🔨 Building scaffolded project..."
dotnet build "$TEMP_DIR" --no-restore
echo "✅ abies-browser template compiles successfully"
rm -rf "$TEMP_DIR"
- name: Smoke test — abies-browser-empty
run: |
TEMP_DIR=$(mktemp -d)
echo "🧪 Scaffolding abies-browser-empty into $TEMP_DIR..."
dotnet new abies-browser-empty -o "$TEMP_DIR"
echo "🔨 Restoring scaffolded project..."
dotnet restore "$TEMP_DIR" --configfile /tmp/template-nuget.config
echo "🔨 Building scaffolded project..."
dotnet build "$TEMP_DIR" --no-restore
echo "✅ abies-browser-empty template compiles successfully"
rm -rf "$TEMP_DIR"
- name: Smoke test — abies-server
run: |
TEMP_DIR=$(mktemp -d)
echo "🧪 Scaffolding abies-server into $TEMP_DIR..."
dotnet new abies-server -o "$TEMP_DIR"
echo "🔨 Restoring scaffolded project..."
dotnet restore "$TEMP_DIR" --configfile /tmp/template-nuget.config
echo "🔨 Building scaffolded project..."
dotnet build "$TEMP_DIR" --no-restore
echo "✅ abies-server template compiles successfully"
rm -rf "$TEMP_DIR"
- name: Security scan scaffolded templates
if: steps.template-changes.outputs.has_template_changes == 'true'
run: |
OUT_ROOT=$(mktemp -d)
echo "Using temp output root: $OUT_ROOT"
dotnet new abies-browser -o "$OUT_ROOT/abies-browser"
dotnet new abies-browser-empty -o "$OUT_ROOT/abies-browser-empty"
dotnet new abies-server -o "$OUT_ROOT/abies-server"
for project in \
"$OUT_ROOT/abies-browser" \
"$OUT_ROOT/abies-browser-empty" \
"$OUT_ROOT/abies-server"; do
dotnet restore "$project" --configfile /tmp/template-nuget.config
dotnet list "$project" package --vulnerable --include-transitive 2>&1 | tee "$project/vulnerability-report.txt"
if grep -qi "critical\|high" "$project/vulnerability-report.txt"; then
echo "❌ High/Critical dependency vulnerabilities in scaffolded project: $project"
exit 1
fi
done
TRIVY_IMAGE=""
for candidate in ghcr.io/aquasecurity/trivy:latest aquasec/trivy:latest; do
if docker pull "$candidate" >/dev/null 2>&1; then
TRIVY_IMAGE="$candidate"
break
fi
done
if [ -z "$TRIVY_IMAGE" ]; then
echo "❌ Could not pull a usable Trivy image."
exit 1
fi
docker run --rm \
-v "$PWD:/src" \
-v "$OUT_ROOT:/scan" \
"$TRIVY_IMAGE" fs \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--scanners vuln,misconfig,secret \
--exit-code 1 \
/scan
python3 -m pip install --user semgrep
export PATH="$HOME/.local/bin:$PATH"
semgrep scan --config .semgrep/rules/template-security.yml "$OUT_ROOT" --error
# Bundle size quality gate
bundle-size-check:
name: Bundle Size Check
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- name: Checkout
uses: actions/checkout@v6
with:
# Nerdbank.GitVersioning requires full git history to compute versions
fetch-depth: 0
- name: Setup .NET 10
uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- name: Install WASM workloads
run: dotnet workload install wasm-experimental wasm-tools
- name: Publish Release (Trimmed)
run: |
dotnet publish Picea.Abies.Conduit.Wasm/Picea.Abies.Conduit.Wasm.csproj \
-c Release
env:
# Suppress NuGet audit — the security-scan job handles vulnerability detection
DOTNET_NUGET_AUDIT: "false"
- name: Measure Bundle Size
id: bundle-size
run: |
# For browser-wasm projects using Microsoft.NET.Sdk, the publish output
# goes to bin/Release/<tfm>/browser-wasm/AppBundle/ (not wwwroot/_framework).
# The _framework directory contains the WASM runtime and assemblies.
PUBLISH_DIR="Picea.Abies.Conduit.Wasm/bin/Release/net10.0/browser-wasm"
# Find the _framework directory in the publish output
FRAMEWORK_DIR=""
for candidate in \
"$PUBLISH_DIR/AppBundle/_framework" \
"$PUBLISH_DIR/publish/wwwroot/_framework" \
"$PUBLISH_DIR/publish/_framework"; do
if [ -d "$candidate" ]; then
FRAMEWORK_DIR="$candidate"
break
fi
done
if [ -z "$FRAMEWORK_DIR" ]; then
echo "❌ Could not find _framework directory in publish output"
echo "Searched in: $PUBLISH_DIR"
echo "Directory contents:"
find "$PUBLISH_DIR" -maxdepth 4 -type d 2>/dev/null || echo " (directory not found)"
exit 1
fi
echo "📂 Found framework directory: $FRAMEWORK_DIR"
# Get total size in bytes
TOTAL_BYTES=$(du -sb "$FRAMEWORK_DIR" | cut -f1)
TOTAL_MB=$((TOTAL_BYTES / 1024 / 1024))
FILE_COUNT=$(find "$FRAMEWORK_DIR" -type f | wc -l)
echo "bundle_size_mb=$TOTAL_MB" >> $GITHUB_OUTPUT
echo "bundle_size_bytes=$TOTAL_BYTES" >> $GITHUB_OUTPUT
echo "file_count=$FILE_COUNT" >> $GITHUB_OUTPUT
echo "📦 WASM Bundle Size Report"
echo "=========================="
echo "Total Size: ${TOTAL_MB}MB ($TOTAL_BYTES bytes)"
echo "File Count: $FILE_COUNT"
echo ""
echo "Largest files:"
find "$FRAMEWORK_DIR" -type f -exec du -h {} + | sort -rh | head -10
- name: Check Bundle Size Limits
run: |
BUNDLE_SIZE=${{ steps.bundle-size.outputs.bundle_size_mb }}
# Validate BUNDLE_SIZE is a valid integer
if ! echo "$BUNDLE_SIZE" | grep -qE '^[0-9]+$'; then
echo "❌ FAILED: Could not determine bundle size (got '$BUNDLE_SIZE')"
exit 1
fi
# Hard limit: 15MB for trimmed Release build
HARD_LIMIT=15
# Soft limit (warning): 10MB
SOFT_LIMIT=10
echo "📊 Bundle Size: ${BUNDLE_SIZE}MB"
echo "🔴 Hard Limit: ${HARD_LIMIT}MB"
echo "🟡 Soft Limit: ${SOFT_LIMIT}MB"
if [ "$BUNDLE_SIZE" -gt "$HARD_LIMIT" ]; then
echo ""
echo "❌ FAILED: Bundle size (${BUNDLE_SIZE}MB) exceeds hard limit (${HARD_LIMIT}MB)"
echo ""
echo "The WASM bundle is too large. Please:"
echo "1. Ensure PublishTrimmed=true is set for Release configuration"
echo "2. Review and remove unnecessary dependencies"
echo "3. Enable InvariantGlobalization if not already done"
echo "4. Consider code splitting if applicable"
exit 1
elif [ "$BUNDLE_SIZE" -gt "$SOFT_LIMIT" ]; then
echo ""
echo "⚠️ WARNING: Bundle size (${BUNDLE_SIZE}MB) exceeds soft limit (${SOFT_LIMIT}MB)"
echo "Consider optimizing bundle size for faster startup times."
else
echo ""
echo "✅ Bundle size is within acceptable limits"
fi
# Check for TODO/FIXME without issues
check-todos:
name: Check TODOs
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check for untracked TODOs
run: |
# Find TODO/FIXME comments without issue references
untracked=$(grep -rIn "TODO\|FIXME" --include="*.cs" --include="*.fs" --exclude-dir=obj --exclude-dir=bin --exclude-dir=.git --binary-files=without-match . | grep -v "#[0-9]" || true)
if [ ! -z "$untracked" ]; then
echo "⚠️ Found TODO/FIXME comments without issue references:"
echo "$untracked"
echo ""
echo "Please either:"
echo "1. Create an issue and reference it (e.g., // TODO #123: description)"
echo "2. Fix the item in this PR"
echo "3. Remove the comment if not needed"
exit 1
else
echo "✅ No untracked TODOs found"
fi
# Summary
pr-validation-summary:
name: PR Validation Summary
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
needs: [validate-pr-title, validate-pr-description, check-pr-size, lint-check, security-scan, template-smoke-test, bundle-size-check, check-todos]
steps:
- name: Check if automated PR
id: check-automated
run: |
if [ "${{ github.event.pull_request.user.login }}" = "dependabot[bot]" ]; then
echo "automated=true" >> $GITHUB_OUTPUT
else
echo "automated=false" >> $GITHUB_OUTPUT
fi
- name: All checks passed
run: |
if [ "${{ steps.check-automated.outputs.automated }}" = "true" ]; then
echo "🤖 Automated PR validation summary"
echo "✅ PR size is reasonable"
echo "✅ Code formatting is correct"
echo "✅ No security vulnerabilities"
echo "✅ Templates scaffold and compile"
echo "✅ Bundle size within limits"
echo "✅ No untracked TODOs"
echo ""
echo "Note: Title and description checks skipped for automated PRs"
else
echo "🎉 All PR validation checks passed!"
echo "✅ PR title follows Conventional Commits"
echo "✅ PR has adequate description"
echo "✅ PR size is reasonable"
echo "✅ Code formatting is correct"
echo "✅ No security vulnerabilities"
echo "✅ Templates scaffold and compile"
echo "✅ Bundle size within limits"
echo "✅ No untracked TODOs"
echo ""
echo "Next steps:"
echo "1. Wait for CD and E2E workflows to complete"
echo "2. Request review from team members"
echo "3. Address any feedback"
echo "4. Merge when approved!"
fi