From a1b12e4c9bddc20faa6a7013a6165f366ae9511f Mon Sep 17 00:00:00 2001 From: Steven Clontz Date: Mon, 20 Oct 2025 10:25:10 +0000 Subject: [PATCH 1/2] script to find and download latest build artifact --- .gitignore | 4 +- scripts/download-latest-artifact.sh | 107 ++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 scripts/download-latest-artifact.sh diff --git a/.gitignore b/.gitignore index 08c79cac4..1141a5b00 100644 --- a/.gitignore +++ b/.gitignore @@ -63,4 +63,6 @@ packages/docs-nextra/public/bundle .next cspell.json *.timestamp-* -.cspell \ No newline at end of file +.cspell + +.cache.zip diff --git a/scripts/download-latest-artifact.sh b/scripts/download-latest-artifact.sh new file mode 100644 index 000000000..c695197e9 --- /dev/null +++ b/scripts/download-latest-artifact.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# Download the most recent artifact from GitHub Actions for this repository. +# Usage: ./scripts/download-latest-artifact.sh +# Behavior: +# - Uses GITHUB_TOKEN env var if present; otherwise will try to read from `git config --get github.token`. +# - If workflow is omitted, searches across recent runs for an artifact with the given name. +# - Downloads the most recent successful run's artifact matching the name and extracts it to ./artifacts/. + +REPO=Doenet/DoenetML +ARTIFACT_NAME=build-artifacts +BRANCH=main + +GITHUB_TOKEN=${GITHUB_TOKEN:-$(git config --get github.token || true)} +if [ -z "$GITHUB_TOKEN" ]; then + echo "Error: GITHUB_TOKEN not set." >&2 + exit 3 +fi + +API="https://api.github.com" +AUTH_HEADER="Authorization: token $GITHUB_TOKEN" + +# Helper to curl JSON with auth +gh_api() { + local url="$1" + curl -sSL -H "Accept: application/vnd.github+json" -H "$AUTH_HEADER" "$url" +} + +# build URL for recent workflow runs (successful) and search for artifact name +# If workflow provided, restrict to that workflow +runs_url() { + echo "$API/repos/$REPO/actions/runs?status=success&per_page=50&branch=$(urlencode "$BRANCH")" +} + +# urlencode helper +urlencode() { + local s="$1" + python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1], safe=''))" "$s" +} + +# Require jq +if ! command -v jq >/dev/null 2>&1; then + echo "Error: jq is required but not installed. Install jq and retry." >&2 + exit 4 +fi + +echo -e "Using API url: $(runs_url)\n" + +runs_json=$(gh_api "$(runs_url)") + +# Iterate runs in order, find artifacts for each run +runs_count=$(printf "%s" "$runs_json" | jq -r '.workflow_runs | length') +if [ "$runs_count" -eq 0 ]; then + echo "No successful workflow runs found matching criteria." >&2 + exit 5 +fi + +ARTIFACT_URL="" +ARTIFACT_ID="" +RUN_ID="" +for i in $(seq 0 $((runs_count-1))); do + run_id=$(printf "%s" "$runs_json" | jq -r ".workflow_runs[$i].id") + # list artifacts for this run + artifacts_url=$(echo "$API/repos/$REPO/actions/runs/$run_id/artifacts") + echo -e "Inspecting artifacts at ${artifacts_url}" + artifacts_json=$(gh_api "$artifacts_url") + + match_count=$(printf "%s" "$artifacts_json" | jq -r ".artifacts | map(select(.name==\"$ARTIFACT_NAME\" and .expired==false)) | length") + if [ "$match_count" -gt 0 ]; then + echo -e "Found non-expired artifact '$ARTIFACT_NAME' in run $run_id.\n" + ARTIFACT_ID=$(printf "%s" "$artifacts_json" | jq -r ".artifacts | map(select(.name==\"$ARTIFACT_NAME\" and .expired==false))[0].id") + ARTIFACT_URL="$API/repos/$REPO/actions/artifacts/$ARTIFACT_ID/zip" + RUN_ID="$run_id" + break + else + echo -e "Non-expired artifact '$ARTIFACT_NAME' not found in run $run_id. Continuing to next run...\n" + fi +done + +if [ -z "$ARTIFACT_URL" ]; then + echo "Artifact named '$ARTIFACT_NAME' not found in recent successful runs." >&2 + exit 6 +fi + +echo -e "Downloading artifact '$ARTIFACT_NAME' from repo $REPO (run $RUN_ID) using URL:" +echo -e "$ARTIFACT_URL\n" + +# Use curl with auth to download zip +zip_path=".cache.zip" +curl -sSL -H "$AUTH_HEADER" -H "Accept: application/vnd.github+json" "$ARTIFACT_URL" -o "$zip_path" + +if [ ! -s "$zip_path" ]; then + echo "Download failed or produced empty file: $zip_path" >&2 + exit 7 +fi + +# Try unzip or bsdtar +if command -v unzip >/dev/null 2>&1; then + unzip -o "$zip_path" >/dev/null +elif command -v bsdtar >/dev/null 2>&1; then + bsdtar -xf "$zip_path" +else + echo "Downloaded artifact to $zip_path. Please extract it manually (no unzip/bsdtar found)." >&2 + exit 0 +fi + +echo "Extracted artifact from $zip_path" +exit 0 From 96fecc1618f4fef7ccda66127adef575eb9ff587 Mon Sep 17 00:00:00 2001 From: Steven Clontz Date: Mon, 20 Oct 2025 13:38:06 +0000 Subject: [PATCH 2/2] add script to devcontainer --- .devcontainer/devcontainer.json | 2 +- README.md | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f549ba3ce..0cea6bedf 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,7 +9,7 @@ "version": "latest" } }, - "postCreateCommand": "npm install && npm run build", + "postCreateCommand": "bash scripts/download-latest-artifact.sh && npm install && npm run build", "customizations": { "vscode": { "extensions": [ diff --git a/README.md b/README.md index 2f13556d5..8782f4f08 100644 --- a/README.md +++ b/README.md @@ -59,8 +59,9 @@ node --version rustc --version ``` -In root directory, run `npm install && npm run build` to install -dependencies and build packages. +In root directory, run +`bash scripts/download-latest-artifact.sh && npm install && npm run build` +to install dependencies and build packages. Note for Windows users: There are currently some [issues](https://github.com/Doenet/DoenetML/pull/326)