Skip to content

Commit 4eeec21

Browse files
themr0cclaude
andauthored
[RHDHBUGS-3010]: Fix GitHub publication workflow (release-1.9) (#2101)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent b07e2fd commit 4eeec21

10 files changed

Lines changed: 302 additions & 295 deletions

File tree

.github/workflows/build-asciidoc.yml

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -68,39 +68,10 @@ jobs:
6868
run: |
6969
echo "Building branch ${{ env.GIT_BRANCH }}"
7070
touch .lycheecache
71-
build/scripts/build-ccutil.sh -b ${{ env.GIT_BRANCH }}
71+
node build/scripts/build-orchestrator.js -b ${{ env.GIT_BRANCH }} --no-cqa
7272
7373
- name: Deploy to the gh-pages branch
7474
env:
7575
GITHUB_TOKEN: ${{ secrets.RHDH_BOT_TOKEN }}
7676
GITHUB_REPOSITORY: ${{ github.repository }}
7777
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

.github/workflows/pr.yml

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ jobs:
9090
with:
9191
ref: ${{ github.event.pull_request.base.ref }}
9292
path: trusted-scripts
93+
sparse-checkout: build/scripts
9394

9495
- name: Checkout PR branch for content to build
9596
uses: actions/checkout@v4
@@ -104,7 +105,7 @@ jobs:
104105
# update
105106
sudo apt-get update -y || true
106107
# install
107-
sudo apt-get -y -q install podman && podman --version
108+
sudo apt-get -y -q install podman rsync && podman --version
108109
echo "GIT_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_ENV
109110
110111
- name: Install lychee
@@ -143,15 +144,12 @@ jobs:
143144
CQA_BASE_REF: base/${{ github.event.pull_request.base.ref }}
144145
run: |
145146
echo "Building PR ${{ github.event.pull_request.number }}"
146-
# Copy trusted build scripts from base branch (not content)
147-
# Guard each file — older branches may not have them
148-
for f in build/scripts/build-ccutil.sh build/scripts/build-orchestrator.js build/scripts/error-patterns.json lychee.toml .lycheeignore; do
149-
if [[ -f "trusted-scripts/$f" ]]; then cp "trusted-scripts/$f" "pr-content/$f"; fi
147+
rm -rf pr-content/build/scripts
148+
rsync -az trusted-scripts/build/scripts pr-content/build/
149+
# some files are new in main/1.10, so check if they exist before copying
150+
for f in trusted-scripts/.lycheeignore trusted-scripts/lychee.toml; do
151+
if [[ -f $f ]]; then rsync -az $f pr-content/; fi
150152
done
151-
if [[ -d "trusted-scripts/build/scripts/cqa" ]]; then
152-
rm -rf pr-content/build/scripts/cqa
153-
cp -r trusted-scripts/build/scripts/cqa pr-content/build/scripts/cqa
154-
fi
155153
touch pr-content/.lycheecache
156154
cd pr-content
157155
# Add base branch as remote so CQA checks can diff PR content against it

.github/workflows/shellcheck.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,16 @@ jobs:
8989
- name: Get changed shell scripts
9090
id: changed-files
9191
run: |
92-
# Get list of changed .sh files
92+
# Get list of changed .sh files that still exist (exclude deletions)
9393
git fetch origin ${{ github.base_ref }}
94-
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '\.sh$' || echo "")
94+
ALL_CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD | grep '\.sh$' || echo "")
95+
CHANGED_FILES=""
96+
while IFS= read -r file; do
97+
if [ -n "$file" ] && [ -f "$file" ]; then
98+
CHANGED_FILES="${CHANGED_FILES:+$CHANGED_FILES
99+
}$file"
100+
fi
101+
done <<< "$ALL_CHANGED"
95102
echo "changed_files<<EOF" >> $GITHUB_OUTPUT
96103
echo "$CHANGED_FILES" >> $GITHUB_OUTPUT
97104
echo "EOF" >> $GITHUB_OUTPUT

.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-cqa.sh

Lines changed: 0 additions & 15 deletions
This file was deleted.

build/scripts/build-orchestrator.js

Lines changed: 30 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
* node build/scripts/build-orchestrator.js -b main
1010
* node build/scripts/build-orchestrator.js -b pr-123 --verbose
1111
* node build/scripts/build-orchestrator.js -b main --jobs 4
12+
* node build/scripts/build-orchestrator.js -b main --no-cqa --no-lychee
1213
*/
1314

1415
import { existsSync, readFileSync, writeFileSync, mkdirSync, rmSync, readdirSync, renameSync, copyFileSync } from 'node:fs';
1516
import { resolve, dirname, join } from 'node:path';
1617
import { spawn } from 'node:child_process';
1718
import { cpus } from 'node:os';
1819
import { fileURLToPath } from 'node:url';
19-
import { get as httpsGet } from 'node:https';
2020
const __filename = fileURLToPath(import.meta.url);
2121
const __dirname = dirname(__filename);
2222

@@ -32,12 +32,14 @@ const SAFE_PATH = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
3232
// ── Argument parsing ─────────────────────────────────────────────────────────
3333

3434
function parseArgs(argv) {
35-
const args = { branch: 'main', verbose: false, jobs: cpus().length };
35+
const args = { branch: 'main', verbose: false, jobs: cpus().length, lychee: true, cqa: true };
3636
for (let i = 2; i < argv.length; i++) {
3737
switch (argv[i]) {
3838
case '-b': args.branch = argv[++i]; break;
3939
case '--verbose': args.verbose = true; break;
4040
case '--jobs': args.jobs = Number.parseInt(argv[++i], 10); break;
41+
case '--no-lychee': args.lychee = false; break;
42+
case '--no-cqa': args.cqa = false; break;
4143
}
4244
}
4345
return args;
@@ -437,51 +439,6 @@ function generateBranchIndex(branch, results, repoRoot) {
437439
writeFileSync(join(indexDir, 'index.html'), html);
438440
}
439441

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-
485442
// ── Summary output ───────────────────────────────────────────────────────────
486443

487444
function printFailedTitle(r) {
@@ -653,31 +610,39 @@ async function main() {
653610
// Generate branch index HTML (only for passed titles)
654611
generateBranchIndex(args.branch, buildResults, repoRoot);
655612

656-
// Update root index
657-
await updateRootIndex(args.branch, repoRoot);
658-
659613
// Run lychee link validation
660-
console.log('\nRunning link validation (lychee)...');
661-
const lycheeResult = await runLychee(repoRoot, args.branch, args.verbose);
662-
if (lycheeResult.errors.length === 0) {
663-
lycheeResult.errors = classifyErrors(lycheeResult.output, patterns);
614+
const skippedResult = { status: 'skipped', duration: 0, output: '', stats: { total: 0, successful: 0, errors: 0, excludes: 0, timeouts: 0 }, errors: [] };
615+
let lycheeResult;
616+
if (args.lychee) {
617+
console.log('\nRunning link validation (lychee)...');
618+
lycheeResult = await runLychee(repoRoot, args.branch, args.verbose);
619+
if (lycheeResult.errors.length === 0) {
620+
lycheeResult.errors = classifyErrors(lycheeResult.output, patterns);
621+
}
622+
} else {
623+
console.log('\nSkipping link validation (--no-lychee)');
624+
lycheeResult = { ...skippedResult };
664625
}
665626

666627
// 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-
})();
628+
const skippedCqa = { status: 'skipped', duration: 0, output: '', stats: { total: 0, pass: 0, fail: 0 } };
629+
let cqaResult;
630+
if (!args.cqa || process.env.CQA_RUNNING) {
631+
if (!args.cqa) console.log('\nSkipping CQA (--no-cqa)');
632+
cqaResult = skippedCqa;
633+
} else {
634+
// Write preliminary report so CQA-14 can read lychee results without rebuilding
635+
const pendingCqa = { status: 'pending', duration: 0, output: '', stats: { total: 0, pass: 0, fail: 0 } };
636+
writeReport(args.branch, buildResults, lycheeResult, pendingCqa, args.jobs, 0, repoRoot);
637+
638+
console.log('\nRunning CQA content quality assessment...');
639+
process.env.CQA_RUNNING = '1';
640+
cqaResult = await runCqa(repoRoot, args.verbose);
641+
delete process.env.CQA_RUNNING;
642+
}
674643

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

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

build/scripts/build.sh

Lines changed: 0 additions & 80 deletions
This file was deleted.

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

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,18 +88,25 @@ 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+
// Detect current branch for correct output directory naming and link remapping.
94+
let branch = 'main';
95+
try {
96+
branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); // NOSONAR
97+
} catch { /* fall back to main */ }
98+
try {
99+
execFileSync('node', ['build/scripts/build-orchestrator.js', '-b', branch, '--no-cqa'], { // NOSONAR — fixed args, no user input
100+
cwd: root,
101+
stdio: 'pipe',
102+
timeout: 600000,
103+
env: { ...process.env, CQA_RUNNING: '1' },
104+
});
105+
} catch {
106+
// Orchestrator exits non-zero when lychee finds broken links; the report is still written
107+
}
102108
}
109+
// When CQA_RUNNING is set: preliminary report already exists with lychee results
103110

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

0 commit comments

Comments
 (0)