diff --git a/.github/workflows/jekyll-build.yml b/.github/workflows/jekyll-build.yml index cd005177..bb20177b 100644 --- a/.github/workflows/jekyll-build.yml +++ b/.github/workflows/jekyll-build.yml @@ -95,20 +95,20 @@ jobs: run: | case "${{ inputs.extract_archive }}" in *.tar.gz|*.tgz) - tar -xzf ${{ inputs.extract_archive }} -C . + tar -xzf "${{ inputs.extract_archive }}" -C . ;; *.tar) - tar -xf ${{ inputs.extract_archive }} -C . + tar -xf "${{ inputs.extract_archive }}" -C . ;; *.zip) - 7z x ${{ inputs.extract_archive }} -o. + 7z x "${{ inputs.extract_archive }}" -o. ;; *) echo "Unsupported archive format" exit 1 ;; esac - rm -f ${{ inputs.extract_archive }} + rm -f "${{ inputs.extract_archive }}" - name: Setup project if: ${{ github.repository == 'LizardByte/LizardByte.github.io' }} diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 00000000..7a5ea1b1 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,20 @@ +--- +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-24.04 + tools: + ruby: "3.3" + jobs: + install: + - chmod +x "./scripts/readthedocs_build.sh" + build: + html: + - "./scripts/readthedocs_build.sh" + +submodules: + include: all + recursive: true diff --git a/README.md b/README.md index adbba30e..c96d0d53 100644 --- a/README.md +++ b/README.md @@ -66,3 +66,77 @@ jobs: ``` For additional options see [jekyll-build.yml](.github/workflows/jekyll-build.yml) + +### PR Previews + +Changes to gh-pages can be previewed, by setting up the project in ReadTheDocs. +It is recommended to use the following manual configuration: + +Initial setup: +```yml +Name: LizardByte-gh-pages- # the project slug cannot be changed after creation +Repository URL: https://github.com/LizardByte/.git +Default-branch: master +Language: English +``` + +Project Settings: +```yml +Connected-repository: https://github.com/LizardByte/.git +URL-versioning-scheme: Multiple versions without translations +Path-for-.readthedocs.yaml: # only set if not in the root of the repo +Programming-Language: Other +Project-homepage: https://app.lizardbyte.dev/ +Description: Preview PRs for gh-pages +Build-pull-requests-for-this-project: true +``` + +Environment Variables: +```yml +# REQUIRED +GITHUB_WORKFLOW: call-jekyll-build / Build Jekyll +SITE_ARTIFACT: prep.zip # the name of the artifact to look for in the workflow + +# OPTIONAL +CONFIG_FILE: _config.yml +EXTRACT_ARCHIVE: pre_built_database.zip # if there is a nested archive to extract +GITHUB_TIMEOUT: 10 # timeout in minutes to use when searching for GitHub artifacts, max 15 +THEME_REF: master +``` + +Additionally, do the following: + +1. Deactivate the `stable` version +2. Make the `latest` version hidden +3. Add project as a subproject of `LizardByte-gh-pages-main` +4. Update branch protection rules in GitHub repo settings to require status checks from ReadTheDocs +5. Add the below `.readthedocs.yaml` file to the root of the repo, or a custom path as specified in the project settings + +```yml +--- +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-24.04 + tools: + ruby: "3.3" + apt_packages: + - 7zip + - jq + jobs: + install: + - | + mkdir -p "./tmp" + branch="master" + base_url="https://raw.githubusercontent.com/LizardByte/LizardByte.github.io" + url="${base_url}/refs/heads/${branch}/scripts/readthedocs_build.sh" + curl -sSL -o "./tmp/readthedocs_build.sh" "${url}" + chmod +x "./tmp/readthedocs_build.sh" + build: + html: + - "./tmp/readthedocs_build.sh" + +``` diff --git a/scripts/readthedocs_build.sh b/scripts/readthedocs_build.sh new file mode 100644 index 00000000..174a4d9d --- /dev/null +++ b/scripts/readthedocs_build.sh @@ -0,0 +1,263 @@ +#!/usr/bin/env bash +set -e + +# REQUIRED ENVIRONMENT VARIABLES (for subprojects) +github_workflow=$(echo "$GITHUB_WORKFLOW" | tr -d "'") +site_artifact=$(echo "$SITE_ARTIFACT" | tr -d "'") + +# OPTIONAL ENVIRONMENT VARIABLES (for subprojects) +config_file=$(echo "$CONFIG_FILE" | tr -d "'") +extract_archive=$(echo "$EXTRACT_ARCHVIE" | tr -d "'") +github_timeout=$GITHUB_TIMEOUT +theme_ref=$THEME_REF + +# From ReadTheDocs +git_sha=$READTHEDOCS_GIT_COMMIT_HASH +github_url=$READTHEDOCS_GIT_CLONE_URL + +if [[ $github_url == git@* ]]; then + # SSH URL format: git@github.com:user/repo.git + github_user=$(echo "$github_url" | cut -d: -f2 | cut -d/ -f1) + github_repo=$(echo "$github_url" | cut -d/ -f2 | sed 's/\.git$//') +else + # HTTPS URL format: https://github.com/user/repo + github_user=$(echo "$github_url" | cut -d/ -f4) + github_repo=$(echo "$github_url" | cut -d/ -f5 | sed 's/\.git$//') +fi + +export PAGES_REPO_NWO="${github_user}/${github_repo}" + +# if timeout is not set then default to 10 minutes +if [ -z "$github_timeout" ]; then + github_timeout=10 +fi + +# if config file is not set then default to _config.yml +if [ -z "$config_file" ]; then + config_file="_config.yml" +fi + +# if theme ref is not set then default to master +if [ -z "$theme_ref" ]; then + theme_ref="master" +fi + +echo "git sha: $git_sha" +echo "github url: $github_url" +echo "github user: $github_user" +echo "github repo: $github_repo" + +# set default directories +project_dir="." +theme_dir="." + +# if not the theme project then we need to clone the theme with submodules +sub_project="false" +if [ "$READTHEDOCS_PROJECT" != "lizardbyte-gh-pages-main" ]; then + sub_project="true" + echo "Building a subproject: $READTHEDOCS_PROJECT" + echo "github workflow: $github_workflow" + echo "site artifact: $site_artifact" + echo "github timeout: $github_timeout" + echo "extract archive: $extract_archive" + echo "config file: $config_file" + echo "theme ref: $theme_ref" + + start_time=$(date +%s) + max_time=$((start_time + 60 * github_timeout)) + sleep_interval=10 + + # fail if the workflow is not set + if [ -z "$github_workflow" ]; then + echo "github_workflow is not set" + exit 1 + fi + + # fail if the site artifact is not set + if [ -z "$site_artifact" ]; then + echo "site_artifact is not set" + exit 1 + fi + + project_dir="project" + theme_dir="theme" + + mkdir -p "${project_dir}" + + git clone https://github.com/LizardByte/LizardByte.github.io.git "${theme_dir}" + pushd "${theme_dir}" + git checkout "${theme_ref}" + git submodule update --init --recursive + popd + + encoded_workflow=$(echo "$github_workflow" | sed 's/ /%20/g') + check_api_url="https://api.github.com/repos/${github_user}/${github_repo}/commits/${git_sha}/check-runs?check_name=${encoded_workflow}" + echo "Check API URL: $check_api_url" + + # Wait for check runs to be available + count=1 + while true; do + current_time=$(date +%s) + if [ $current_time -gt $max_time ]; then + echo "Timeout waiting for check runs" + exit 1 + fi + echo "Checking check runs: $count" + + response=$(curl -s -H "Accept: application/vnd.github.v3+json" "$check_api_url") + check_runs=$(echo "$response" | jq -r '.check_runs') + check_run_count=$(echo "$check_runs" | jq -r 'length') + + echo "Check runs count: $check_run_count" + + if [ "$check_run_count" -gt 0 ]; then + check_run=$(echo "$check_runs" | jq -r '.[0]') + check_job_id=$(echo "$check_run" | jq -r '.id') + check_run_html_url=$(echo "$check_run" | jq -r '.html_url') + echo "Check job id: $check_job_id" + echo "Check run URL: $check_run_html_url" + break + fi + + echo "Waiting for check runs to be available..." + sleep $sleep_interval + count=$((count + 1)) + done + + # get the run id from the html url + # e.g. https://github.com/LizardByte/LizardByte.github.io/actions/runs/13687305039/job/38273540489 + check_run_id=$(echo "$check_run_html_url" | cut -d/ -f8) + echo "Check run id: $check_run_id" + + # wait for the check run to complete, cancelled, timed out, etc. + check_run_api_url="https://api.github.com/repos/${github_user}/${github_repo}/actions/runs/${check_run_id}" + echo "Check run API URL: $check_run_api_url" + + count=1 + while true; do + current_time=$(date +%s) + if [ $current_time -gt $max_time ]; then + echo "Timeout waiting for check runs" + exit 1 + fi + echo "Checking check run status: $count" + + check_run_response=$(curl -s -H "Accept: application/vnd.github.v3+json" "$check_run_api_url") + + check_run_status=$(echo "$check_run_response" | jq -r '.status') + check_run_conclusion=$(echo "$check_run_response" | jq -r '.conclusion') + + echo "Check run status: $check_run_status" + if [ "$check_run_status" == "completed" ]; then + break + fi + + echo "Waiting for check run to complete..." + sleep $sleep_interval + count=$((count + 1)) + done + + # if not successful then exit + if [ "$check_run_conclusion" != "success" ]; then + echo "Check run did not complete successfully" + exit 1 + fi + + # download the artifact using nightly.link + artifact_url="https://nightly.link/${github_user}/${github_repo}/actions/runs/${check_run_id}/${site_artifact}" + + # download and extract the ZIP artifact + curl -sL "$artifact_url" -o "${project_dir}/artifact.zip" + 7z x "${project_dir}/artifact.zip" -o"${project_dir}" + rm "${project_dir}/artifact.zip" + + # if there is a name provided for extract_artifact, then we will extract the nested archive + if [ -n "$extract_archive" ]; then + pushd "${project_dir}" + case "$extract_archive" in + *.tar.gz|*.tgz) + tar -xzf "$extract_archive" -C . + ;; + *.tar) + tar -xf "$extract_archive" -C . + ;; + *.zip) + 7z x "$extract_archive" -o. + ;; + *) + echo "Unsupported archive format" + exit 1 + ;; + esac + rm -f "$extract_archive" + popd + fi +fi + +TMPDIR=$(pwd)/../tmp +mkdir -p "${TMPDIR}" + +base_dirs=( + "${theme_dir}/third-party/beautiful-jekyll" + "${theme_dir}" +) + +targets=( + *.gemspec + _data + _includes + _layouts + _sass + assets + 404.html + _config_theme.yml + favicon.ico + feed.xml + Gemfile + staticman.yml + tags.html +) + +for base_dir in "${base_dirs[@]}"; do + for target in "${targets[@]}"; do + if [ -e "$base_dir/$target" ]; then + cp -rf "$base_dir/$target" "${TMPDIR}/" + fi + done +done + +# copy project directory, they should only come from the project repo +cp -RTf "${project_dir}/" "${TMPDIR}/" + +cd "${TMPDIR}" + +gem install bundle +bundle install +echo "baseurl: $READTHEDOCS_VERSION" > _config_rtd.yml +echo "_config_rtd.yml:" +cat _config_rtd.yml +echo "_config_theme.yml:" +cat _config_theme.yml + +config_files=_config_rtd.yml,_config_theme.yml +if [ -n "$config_file" ] && [ -e "$config_file" ]; then + config_files="${config_files},$config_file" + echo "config file: $config_file" + cat "$config_file" +fi + +bundle exec jekyll build \ + --future \ + --config $config_files \ + --destination "${READTHEDOCS_OUTPUT}html" + +# mimic gh-pages +if [ "$sub_project" == "true" ]; then + mkdir -p "${READTHEDOCS_OUTPUT}html/${github_repo}/assets" + cp -RTf "${READTHEDOCS_OUTPUT}html/assets" "${READTHEDOCS_OUTPUT}html/${github_repo}/assets" +fi + +echo "Build finished" +echo "Output directory: ${READTHEDOCS_OUTPUT}html" +echo "Listing output directory:" +ls -Ra "$READTHEDOCS_OUTPUT"