chore: scaffold tracks and tooling

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Lucas Winther
2026-10-28 23:34:21 +01:00
co-authored by Claude Opus 5.5
parent 6dc5bdde9e
commit e150b7fefb
27 changed files with 282 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
#!/usr/bin/env bash
# Usage: scripts/check-stubs.sh <js|scala|fsharp>
# Runs each module M01..M11 that has tests against the stubs in src/. Fails if any module's tests pass.
set -uo pipefail
track="${1:?usage: check-stubs.sh <js|scala|fsharp>}"
repo="$(cd "$(dirname "$0")/.." && pwd)"
dir="$repo/$track"
case "$track" in
js) has() { compgen -G "$dir/test/m$1-*.test.js" >/dev/null; } ;;
scala) has() { compgen -G "$dir/test/M$1*.test.scala" >/dev/null; } ;;
fsharp) has() { compgen -G "$dir/tests/M$1*.fs" >/dev/null; } ;;
*) echo "unknown track: $track" >&2; exit 2 ;;
esac
status=0
for n in 01 02 03 04 05 06 07 08 09 10 11; do
m="M$n"
if ! has "$n"; then echo "$m: no tests"; continue; fi
if (cd "$dir" && ./test.sh "$m") >/dev/null 2>&1; then
echo "$m: FAIL (tests pass against stubs)"; status=1
else
echo "$m: ok (fails against stubs)"
fi
done
exit $status
+30
View File
@@ -0,0 +1,30 @@
// Tiny zero-dependency static file server for the repo root. Tooling, not course code.
// Usage: node scripts/serve.mjs [port] (default 8000)
import { createServer } from 'node:http'
import { readFile, stat } from 'node:fs/promises'
import { extname, join, normalize, resolve, sep } from 'node:path'
import { fileURLToPath } from 'node:url'
const root = resolve(fileURLToPath(new URL('..', import.meta.url)))
const port = Number(process.argv[2] ?? 8000)
const types = {
'.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8',
'.mjs': 'text/javascript; charset=utf-8', '.json': 'application/json', '.css': 'text/css',
'.md': 'text/markdown; charset=utf-8', '.svg': 'image/svg+xml', '.png': 'image/png',
'.wasm': 'application/wasm', '.map': 'application/json',
}
createServer(async (req, res) => {
try {
const path = normalize(decodeURIComponent(new URL(req.url, 'http://x').pathname))
let file = join(root, path)
if (file !== root && !file.startsWith(root + sep)) throw Object.assign(new Error(), { code: 'EACCES' })
if ((await stat(file)).isDirectory()) file = join(file, 'index.html')
const body = await readFile(file)
res.writeHead(200, { 'content-type': types[extname(file)] ?? 'application/octet-stream' })
res.end(body)
} catch (e) {
res.writeHead(e.code === 'EACCES' ? 403 : 404, { 'content-type': 'text/plain' })
res.end(e.code === 'EACCES' ? 'forbidden' : 'not found')
}
}).listen(port, () => console.log(`Serving ${root} on http://localhost:${port}`))
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Usage: scripts/verify.sh <js|scala|fsharp>
# Copies the repo to a temp dir, swaps src/ for solutions/, runs the track's full test suite.
set -euo pipefail
track="${1:?usage: verify.sh <js|scala|fsharp>}"
case "$track" in js|scala|fsharp) ;; *) echo "unknown track: $track" >&2; exit 2;; esac
repo="$(cd "$(dirname "$0")/.." && pwd)"
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
tar -C "$repo" --exclude=.git --exclude=node_modules --exclude=bin --exclude=obj \
--exclude=.scala-build --exclude=.bsp --exclude=.superpowers -cf - . | tar -C "$tmp" -xf -
rm -rf "$tmp/$track/src"
cp -r "$tmp/$track/solutions" "$tmp/$track/src"
cd "$tmp/$track"
./test.sh