diff --git a/.github/workflows/continuous-deployment.yml b/.github/workflows/continuous-deployment.yml index f31d4f8..d783e30 100644 --- a/.github/workflows/continuous-deployment.yml +++ b/.github/workflows/continuous-deployment.yml @@ -87,6 +87,26 @@ jobs: env: JEKYLL_BASE_PATH: /blogs run: npm run build + - name: Verify exported documentation pages + # A partially failed content compile must never reach production as a + # docs-less site. compile-content fails the build on clone/copy errors; + # this is the independent belt-and-braces check on the final artifact. + run: | + set -euo pipefail + for page in out/index.html out/docs/index.html out/docs/getting-started/index.html out/docs/reference/index.html; do + if [ ! -f "$page" ]; then + echo "Missing expected page: $page" + exit 1 + fi + done + reference_count=$(find out/docs/reference -name index.html | wc -l) + echo "Reference pages exported: $reference_count" + # The docs repo currently holds ~240 reference entries; well under + # half of that means the compile silently lost content. + if [ "$reference_count" -lt 100 ]; then + echo "Only $reference_count reference pages exported - documentation content looks incomplete." + exit 1 + fi - name: Download DocumentDB packages from latest release run: .github/scripts/download_packages.sh - name: Verify generated package components diff --git a/.gitignore b/.gitignore index b245cd3..5d29287 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,10 @@ # Temporary content cloning directory _tmp/ -# Reference files -api-reference/ -reference/ +# Reference files (compiled into the repo root from documentdb/docs; anchored +# so the patterns cannot swallow tracked paths like app/docs/reference/) +/api-reference/ +/reference/ # Documentation articles articles/*/ diff --git a/scripts/compile-content.tsx b/scripts/compile-content.tsx index 5e1127d..a69e5f3 100644 --- a/scripts/compile-content.tsx +++ b/scripts/compile-content.tsx @@ -180,22 +180,45 @@ async function cloneContent( const repoName = source.repository.split('/').pop() || 'repo'; const cloneDir = path.join(TEMP_DIR, repoName); - // Clone the repository - spawnSync( + // Clone the repository. A failed clone must fail the build: the copy + // loop below finds nothing to copy and the site would otherwise deploy + // with empty documentation and reference sections. + const cloneResult = spawnSync( 'git', ['clone', '--depth', '1', '--branch', source.branch, source.repository, cloneDir], { stdio: 'pipe' } ); + if (cloneResult.error) { + throw new Error( + `git clone failed for ${source.repository}: ${cloneResult.error.message}` + ); + } + + if (cloneResult.status !== 0) { + const stderr = cloneResult.stderr?.toString().trim(); + throw new Error( + `git clone failed for ${source.repository} (branch ${source.branch}): ${stderr || `exit code ${cloneResult.status}`}` + ); + } + // Process each mapping for (const mapping of source.mappings) { const sourceDir = path.join(cloneDir, mapping.source); const targetDir = path.join(process.cwd(), mapping.target); + if (!fs.existsSync(sourceDir)) { + throw new Error( + `Source folder "${mapping.source}" does not exist in ${source.repository}; the repository layout may have changed.` + ); + } + // Clean target directory cleanDirectory(targetDir); ensureDirectory(targetDir); + const copiedBefore = tracker.getCopiedFiles().length; + // Copy files with filtering copyFilesRecursive( sourceDir, @@ -205,6 +228,12 @@ async function cloneContent( tracker, source.repository ); + + if (tracker.getCopiedFiles().length === copiedBefore) { + throw new Error( + `No files matched for mapping "${mapping.source}" -> "${mapping.target}" from ${source.repository}; refusing to build with empty documentation content.` + ); + } } }