Skip to content

Merge pull request #8 from luke-b/copilot/explore-codebase-emulation-… #95

Merge pull request #8 from luke-b/copilot/explore-codebase-emulation-…

Merge pull request #8 from luke-b/copilot/explore-codebase-emulation-… #95

---
name: Build Wolfenstein 3D
on:
- push
- pull_request
permissions:
contents: write
pages: write
id-token: write
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
RETAINED_GH_PAGES_BUILDS: 3
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install dosbox-x and xvfb
run: |
sudo apt-get install -y xvfb
sudo snap install dosbox-x
- name: Run DosBox to build Wolf3D
run: |
export TERM=dumb
xvfb-run --auto-servernum dosbox-x -silent -debug -conf dosbox.conf -c "mount c . && c: && call autoexec.bat" -log-con -exit
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-dos
path: WOLF3D
- name: Upload console log
uses: actions/upload-artifact@v4
with:
name: dosbox-console-log
path: dosbox_console_log.txt
if-no-files-found: ignore
generate-html:
runs-on: ubuntu-latest
needs: build
steps:
- name: Download build artifact
uses: actions/download-artifact@v4.1.3
with:
name: build-dos
- name: List files in artifact directory
run: ls -R .
- name: Zip the build output
run: |
zip -r ./wolf3d.zip .
- name: Base64 encode the build output
run: |
base64 ./wolf3d.zip > wolf3d_base64.txt
- name: Create HTML file
run: |
echo '<!DOCTYPE html>' > wolf3d.html
echo '<html>' >> wolf3d.html
echo '<head>' >> wolf3d.html
echo '<script src="https://js-dos.com/6.22/current/js-dos.js"></script>' >> wolf3d.html
echo '</head>' >> wolf3d.html
echo '<body>' >> wolf3d.html
echo '<h1>Wolfenstein 3D</h1>' >> wolf3d.html
echo '<div id="jsdos" style="width: 640px; height: 400px;"></div>' >> wolf3d.html
echo '<script>' >> wolf3d.html
echo 'async function initDosBox() {' >> wolf3d.html
echo ' const dos = await Dos(ctx, { wdosboxUrl: "https://js-dos.com/6.22/current/wdosbox.js" });' >> wolf3d.html
echo ' const zipFile = `' >> wolf3d.html
cat wolf3d_base64.txt >> wolf3d.html
echo '`;' >> wolf3d.html
echo ' const zipBlob = await fetch("data:application/zip;base64," + zipFile).then(r => r.blob());' >> wolf3d.html
echo ' await dos.fs.extract(zipBlob);' >> wolf3d.html
echo ' dos.run("WOLF3D.EXE");' >> wolf3d.html
echo '}' >> wolf3d.html
echo 'initDosBox();' >> wolf3d.html
echo '</script>' >> wolf3d.html
echo '</body>' >> wolf3d.html
echo '</html>' >> wolf3d.html
- name: Upload HTML file
uses: actions/upload-artifact@v4
with:
name: wolf3d-html
path: wolf3d.html
deploy-demo:
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Download build artifact
uses: actions/download-artifact@v4.1.3
with:
name: build-dos
path: WOLF3D
- name: Assert wolf3d.exe is present
run: |
echo "Files in WOLF3D/:"
ls -la WOLF3D/
if [ ! -f WOLF3D/WOLF3D.EXE ]; then
echo "ERROR: WOLF3D.EXE not found in build artifact" >&2
exit 1
fi
- name: Create dosbox.conf for jsdos
run: |
cat > WOLF3D/dosbox.conf << 'EOF'
[cpu]
cycles=max
[autoexec]
mount c .
c:
WOLF3D.EXE
EOF
- name: Package hra.jsdos
run: |
cd WOLF3D
zip -r ../hra.jsdos .
cd ..
echo "Created hra.jsdos ($(du -sh hra.jsdos | cut -f1))"
- name: Compute short SHA
id: sha
run: echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Generate index.html
run: |
SHORT_SHA="${{ steps.sha.outputs.short }}"
cat > index.html << EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Wolfenstein 3D — build ${SHORT_SHA}</title>
<link rel="icon" type="image/svg+xml" href="../../../favicon.svg">
<link rel="stylesheet" href="https://js-dos.com/v8/js-dos.css">
<script src="https://js-dos.com/v8/js-dos.js"></script>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: #111; color: #eee; font-family: Arial, sans-serif; display: flex; flex-direction: column; align-items: center; min-height: 100vh; padding: 20px; }
h1 { margin-bottom: 12px; font-size: 1.4rem; }
#dos { width: 800px; height: 600px; max-width: 100%; }
#status { margin-top: 10px; min-height: 1.2em; font-size: 0.95rem; color: #b7c4d6; }
#status.error { color: #ff9b9b; }
.meta { margin-top: 10px; font-size: 0.8rem; color: #888; }
button { margin-top: 10px; padding: 6px 16px; background: #333; color: #eee; border: 1px solid #555; cursor: pointer; border-radius: 4px; }
button:hover { background: #444; }
</style>
</head>
<body>
<h1>Wolfenstein 3D</h1>
<div id="dos"></div>
<p id="status" role="status" aria-live="polite">Loading emulator…</p>
<button onclick="document.getElementById('dos').requestFullscreen && document.getElementById('dos').requestFullscreen()">Full screen</button>
<p class="meta">build ${SHORT_SHA} &mdash; <a href="https://github.com/luke-b/DosTest/commit/${GITHUB_SHA}" style="color:#888">view commit</a></p>
<script>
const dosRoot = document.getElementById("dos");
const status = document.getElementById("status");
function updateStatus(message, isError = false) {
status.textContent = message;
status.classList.toggle("error", isError);
}
async function startDemo() {
if (typeof Dos !== "function") {
console.error("js-dos failed to load before initialization.");
updateStatus("The js-dos runtime could not be loaded. Check your connection and try again.", true);
return;
}
try {
const dos = Dos(dosRoot, {
url: "./hra.jsdos",
onprogress: (stage, total, loaded) => {
if (stage === "Resolving DosBox") {
return;
}
const suffix = total ? ` (${loaded}/${total})` : "";
console.log(stage, loaded, "/", total);
updateStatus(`${stage}${suffix}`);
}
});
if (dos && typeof dos.then === "function") {
await dos;
}
updateStatus("Ready. Click inside the emulator window to start playing.");
} catch (error) {
console.error("Error initializing DOSBox:", error);
updateStatus("The emulator failed to start. Check the browser console for details.", true);
}
}
startDemo();
</script>
</body>
</html>
EOF
- name: Generate latest redirect
run: |
SHORT_SHA="${{ steps.sha.outputs.short }}"
mkdir -p latest-redirect
cat > latest-redirect/index.html << EOF
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="0; url=../${SHORT_SHA}/">
<title>Redirecting…</title>
</head>
<body>
<p>Redirecting to <a href="../${SHORT_SHA}/">latest build (${SHORT_SHA})</a>…</p>
</body>
</html>
EOF
- name: Stage demo deployment
run: |
SHORT_SHA="${{ steps.sha.outputs.short }}"
mkdir -p "staging/demo/build/${SHORT_SHA}" "staging/demo/build/latest"
cp index.html "staging/demo/build/${SHORT_SHA}/index.html"
cp hra.jsdos "staging/demo/build/${SHORT_SHA}/hra.jsdos"
cp latest-redirect/index.html "staging/demo/build/latest/index.html"
cp ./index.html staging/index.html
cp ./js-dos-test.html staging/js-dos-test.html
cp ./jsdos-demo1.html staging/jsdos-demo1.html
cp ./favicon.svg staging/favicon.svg
touch staging/.nojekyll
- name: Deploy demo to gh-pages
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./staging
keep_files: true
- name: Smoke test deployed demo
run: |
SHORT_SHA="${{ steps.sha.outputs.short }}"
for url in \
"https://luke-b.github.io/DosTest/" \
"https://luke-b.github.io/DosTest/demo/build/${SHORT_SHA}/" \
"https://luke-b.github.io/DosTest/demo/build/latest/"
do
echo "Checking ${url}"
curl --fail --silent --show-error --location \
--retry 3 --retry-delay 5 --retry-all-errors \
"${url}" > /dev/null
done
- name: Checkout gh-pages for pruning
uses: actions/checkout@v4
with:
ref: gh-pages
path: gh-pages-prune
token: ${{ secrets.GITHUB_TOKEN }}
- name: Prune old gh-pages builds
run: |
set -euo pipefail
cd gh-pages-prune
build_root="demo/build"
mkdir -p "${build_root}"
retain_count="${RETAINED_GH_PAGES_BUILDS}"
case "${retain_count}" in
''|*[!0-9]*|0)
echo "RETAINED_GH_PAGES_BUILDS must be set to a positive integer." >&2
exit 1
;;
esac
build_timestamps_file="$(mktemp)"
while read -r dir_path; do
dir="$(basename "${dir_path}")"
[ "${dir}" = "latest" ] && continue
if [ ! -d "${build_root}/${dir}" ]; then
echo "Build directory ${build_root}/${dir} does not exist." >&2
exit 1
fi
if ! ts="$(git log -1 --format=%ct -- "${build_root}/${dir}" 2>&1)"; then
echo "Failed to determine timestamp for ${build_root}/${dir}: ${ts}" >&2
exit 1
fi
printf '%s %s\n' "${ts}" "${dir}" >> "${build_timestamps_file}"
done < <(find "${build_root}" -mindepth 1 -maxdepth 1 -type d)
mapfile -t sha_build_dirs < <(sort -rn "${build_timestamps_file}" | awk '{print $2}' | grep -v '^$')
if [ "${#sha_build_dirs[@]}" -le "${retain_count}" ]; then
echo "No old builds to prune."
exit 0
fi
for dir in "${sha_build_dirs[@]:${retain_count}}"; do
echo "Removing old build ${dir}"
rm -rf "${build_root}/${dir}"
done
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add --all "${build_root}"
if ! git diff --cached --quiet; then
git commit -m "chore: prune old demo builds [skip ci]"
git push
fi
update-readme:
runs-on: ubuntu-latest
needs: deploy-demo
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- name: Checkout main
uses: actions/checkout@v4
with:
ref: main
token: ${{ secrets.GITHUB_TOKEN }}
- name: Compute short SHA
id: sha
run: echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Update demo link in README.md
run: |
DEMO_URL="https://luke-b.github.io/DosTest/"
BADGE_LINE="[![Play latest build](https://img.shields.io/badge/Play-Latest%20Build-brightgreen)](${DEMO_URL})"
BLOCK="<!-- DEMO_LINK -->\n${BADGE_LINE}\n<!-- /DEMO_LINK -->"
if grep -q '<!-- DEMO_LINK -->' README.md; then
# Replace existing block (handles multi-line with perl)
perl -i -0pe "s|<!-- DEMO_LINK -->.*?<!-- /DEMO_LINK -->|${BLOCK}|s" README.md
else
# Sentinel missing: insert complete block after the first heading line
perl -i -0pe "s|(# [^\n]+\n)|\$1\n${BLOCK}\n|" README.md
fi
- name: Commit and push README update
run: |
SHORT_SHA="${{ steps.sha.outputs.short }}"
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add README.md
git diff --cached --quiet || git commit -m "chore: update demo link to build ${SHORT_SHA} [skip ci]"
git push