Skip to content

Commit 635bb0f

Browse files
themr0cclaude
andcommitted
[RHDHBUGS-3010]: Fix CQA-14 PR build destruction, rewrite deploy as Node.js
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b07e2fd commit 635bb0f

7 files changed

Lines changed: 370 additions & 203 deletions

File tree

.github/workflows/build-asciidoc.yml

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -70,37 +70,8 @@ jobs:
7070
touch .lycheecache
7171
build/scripts/build-ccutil.sh -b ${{ env.GIT_BRANCH }}
7272
73-
- name: Deploy to the gh-pages branch
73+
- name: Deploy to gh-pages
7474
env:
7575
GITHUB_TOKEN: ${{ secrets.RHDH_BOT_TOKEN }}
7676
GITHUB_REPOSITORY: ${{ github.repository }}
77-
run: bash build/scripts/deploy-gh-pages.sh ./titles-generated --message "Deploy ${{ env.GIT_BRANCH }}"
78-
79-
- name: Cleanup merged PR branches
80-
run: |
81-
PULL_URL="https://api.github.com/repos/redhat-developer/red-hat-developers-documentation-rhdh/pulls"
82-
GITHUB_TOKEN="${{ secrets.RHDH_BOT_TOKEN }}"
83-
git config user.name "rhdh-bot service account"
84-
git config user.email "rhdh-bot@redhat.com"
85-
86-
git checkout gh-pages; git pull || true
87-
dirs=$(find . -maxdepth 1 -name "pr-*" -type d | sed -r -e "s|^\./pr-||")
88-
refs=$(cat pulls.html | grep pr- | sed -r -e "s|.+.html>pr-([0-9]+)</a>.+|\1|")
89-
for d in $(echo -e "$dirs\n$refs" | sort -uV); do
90-
PR="${d}"
91-
echo -n "Check merge status of PR $PR ... "
92-
PR_JSON=$(curl -sSL -H "Accept: application/vnd.github+json" -H "Authorization: Bearer $GITHUB_TOKEN" "$PULL_URL/$PR")
93-
if [[ $(echo "$PR_JSON" | grep merged\") == *"merged\": true"* ]]; then
94-
echo "merged, can delete from pulls.html and remove folder $d"
95-
git rm -fr --quiet "pr-${d}" || rm -fr "pr-${d}"
96-
sed -r -e "/pr-$PR\/index.html>pr-$PR</d" -i pulls.html
97-
elif [[ $(echo "$PR_JSON" | grep \"state\") == *"state\": \"closed\""* ]]; then
98-
echo "closed, can delete from pulls.html and remove folder pr-${d}"
99-
git rm -fr --quiet "pr-${d}" || rm -fr "pr-${d}"
100-
sed -r -e "/pr-$PR\/index.html>pr-$PR</d" -i pulls.html
101-
else
102-
echo "PR is not closed or merged (or could not read API)"
103-
fi
104-
done
105-
git commit -s -m "remove merged PR branches" . || true # don't fail if there's nothing to do
106-
git push origin gh-pages || true # don't fail if there's nothing to do
77+
run: node build/scripts/deploy-gh-pages.js --publish-dir ./titles-generated --message "Deploy ${{ env.GIT_BRANCH }}"

.github/workflows/pr.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ jobs:
197197
env:
198198
GITHUB_TOKEN: ${{ secrets.RHDH_BOT_TOKEN }}
199199
GITHUB_REPOSITORY: ${{ github.repository }}
200-
run: bash trusted-scripts/build/scripts/deploy-gh-pages.sh ./pr-content/titles-generated --message "Deploy PR ${{ github.event.number }} preview"
200+
run: node trusted-scripts/build/scripts/deploy-gh-pages.js --publish-dir ./pr-content/titles-generated --message "Deploy PR ${{ github.event.number }} preview"
201201

202202
# Post one consolidated PR comment with build results, preview link, and CQA checklist.
203203
# Preview link is always shown; marked stale when title build failed (HTML not generated).

.lycheeignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,6 @@ file://.*titles-generated/index\.html$
88

99
# External sites that block automated requests
1010
^https://fonts\.google\.com/icons
11+
12+
# Release notes site is behind Red Hat VPN, unreachable from GitHub Actions
13+
^https://red-hat-developers-documentation\.pages\.redhat\.com/red-hat-developer-hub-release-notes

build/scripts/build-orchestrator.js

Lines changed: 13 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import { resolve, dirname, join } from 'node:path';
1616
import { spawn } from 'node:child_process';
1717
import { cpus } from 'node:os';
1818
import { fileURLToPath } from 'node:url';
19-
import { get as httpsGet } from 'node:https';
2019
const __filename = fileURLToPath(import.meta.url);
2120
const __dirname = dirname(__filename);
2221

@@ -437,51 +436,6 @@ function generateBranchIndex(branch, results, repoRoot) {
437436
writeFileSync(join(indexDir, 'index.html'), html);
438437
}
439438

440-
function fetchUrl(url) {
441-
return new Promise((resolve, reject) => {
442-
httpsGet(url, (res) => {
443-
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
444-
fetchUrl(res.headers.location).then(resolve, reject);
445-
return;
446-
}
447-
if (res.statusCode !== 200) {
448-
res.resume();
449-
reject(new Error(`HTTP ${res.statusCode}`));
450-
return;
451-
}
452-
let data = '';
453-
res.on('data', (chunk) => { data += chunk; });
454-
res.on('end', () => resolve(data));
455-
res.on('error', reject);
456-
}).on('error', reject);
457-
});
458-
}
459-
460-
async function updateRootIndex(branch, repoRoot) {
461-
const isPR = branch.startsWith('pr-');
462-
const indexFile = isPR ? 'pulls.html' : 'index.html';
463-
const indexPath = join(repoRoot, 'titles-generated', indexFile);
464-
const url = `${PAGES_BASE}/${indexFile}`;
465-
466-
// Fetch existing index from GitHub Pages
467-
try {
468-
const data = await fetchUrl(url);
469-
writeFileSync(indexPath, data);
470-
} catch {
471-
// If fetch fails, create a minimal file
472-
writeFileSync(indexPath, '<html><body><ul>\n</ul></body></html>');
473-
}
474-
475-
const content = readFileSync(indexPath, 'utf8');
476-
const link = `./${branch}/index.html`;
477-
if (!content.includes(link)) {
478-
console.log(`Building root index for ${branch} in titles-generated/${indexFile} ...`);
479-
const entry = `<li><a href=${link}>${branch}</a></li>`;
480-
const updated = content.replace('</ul>', `${entry}\n</ul>`);
481-
writeFileSync(indexPath, updated);
482-
}
483-
}
484-
485439
// ── Summary output ───────────────────────────────────────────────────────────
486440

487441
function printFailedTitle(r) {
@@ -653,9 +607,6 @@ async function main() {
653607
// Generate branch index HTML (only for passed titles)
654608
generateBranchIndex(args.branch, buildResults, repoRoot);
655609

656-
// Update root index
657-
await updateRootIndex(args.branch, repoRoot);
658-
659610
// Run lychee link validation
660611
console.log('\nRunning link validation (lychee)...');
661612
const lycheeResult = await runLychee(repoRoot, args.branch, args.verbose);
@@ -664,20 +615,22 @@ async function main() {
664615
}
665616

666617
// Run CQA content quality assessment
667-
// Skip when CQA_RUNNING env is set (CQA-14 recursion guard)
668-
const cqaResult = (process.env.CQA_RUNNING)
669-
? { status: 'skipped', duration: 0, output: '', stats: { total: 0, pass: 0, fail: 0 } }
670-
: await (async () => {
671-
console.log('\nRunning CQA content quality assessment...');
672-
return runCqa(repoRoot, args.verbose);
673-
})();
618+
let cqaResult;
619+
if (process.env.CQA_RUNNING) {
620+
cqaResult = { status: 'skipped', duration: 0, output: '', stats: { total: 0, pass: 0, fail: 0 } };
621+
} else {
622+
// Write preliminary report so CQA-14 can read lychee results without rebuilding
623+
const pendingCqa = { status: 'pending', duration: 0, output: '', stats: { total: 0, pass: 0, fail: 0 } };
624+
writeReport(args.branch, buildResults, lycheeResult, pendingCqa, args.jobs, 0, repoRoot);
625+
626+
console.log('\nRunning CQA content quality assessment...');
627+
process.env.CQA_RUNNING = '1';
628+
cqaResult = await runCqa(repoRoot, args.verbose);
629+
delete process.env.CQA_RUNNING;
630+
}
674631

675632
const totalDuration = Math.round((Date.now() - totalStart) / 1000);
676-
677-
// Print summary
678633
printSummary(buildResults, lycheeResult, cqaResult, patterns, totalDuration);
679-
680-
// Write JSON report
681634
writeReport(args.branch, buildResults, lycheeResult, cqaResult, args.jobs, totalDuration, repoRoot);
682635

683636
// Exit with error if any builds, lychee, or CQA failed

build/scripts/cqa/checks/cqa-14-no-broken-links.js

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,20 @@ function getLycheeIssues(root) {
8888
if (_lycheeIssuesCache !== null) return _lycheeIssuesCache;
8989
_lycheeIssuesCache = [];
9090

91-
try {
92-
// Run build orchestrator (builds fresh HTML + runs lychee with remapping)
93-
// Set CQA_RUNNING to prevent build-orchestrator from running CQA again (recursion)
94-
execFileSync('node', ['build/scripts/build-orchestrator.js', '-b', 'main'], { // NOSONAR — fixed args, no user input
95-
cwd: root,
96-
stdio: 'pipe',
97-
timeout: 600000, // 10 minutes
98-
env: { ...process.env, CQA_RUNNING: '1' },
99-
});
100-
} catch {
101-
// Build may exit non-zero if lychee finds broken links — that's expected
91+
if (!process.env.CQA_RUNNING) {
92+
// Standalone mode: build current state to get lychee results
93+
try {
94+
execFileSync('node', ['build/scripts/build-orchestrator.js', '-b', 'main'], { // NOSONAR — fixed args, no user input
95+
cwd: root,
96+
stdio: 'pipe',
97+
timeout: 600000,
98+
env: { ...process.env, CQA_RUNNING: '1' },
99+
});
100+
} catch {
101+
// Build may exit non-zero if lychee finds broken links — that's expected
102+
}
102103
}
104+
// When CQA_RUNNING is set: preliminary report already exists with lychee results
103105

104106
// Read the build report
105107
const reportPath = join(root, 'build-report.json');

0 commit comments

Comments
 (0)