/** Bash helpers shared by every generated job script: step outputs/env files, hashFiles() and the R2-backed cache. */ const SOURCE = String.raw`__gitfor_re_heredoc='^([A-Za-z_][A-Za-z0-9_-]*)<<(.+)$' __gitfor_re_kv='^([A-Za-z_][A-Za-z0-9_-]*)=(.*)$' __gitfor_read_kv() { [ -s "$1" ] || return 0 local __line __key __value __delim __more while IFS= read -r __line || [ -n "$__line" ]; do if [[ $__line =~ $__gitfor_re_heredoc ]]; then __key="#{BASH_REMATCH[1]}"; __delim="#{BASH_REMATCH[2]}"; __value=""; __more=0 while IFS= read -r __line || [ -n "$__line" ]; do [ "$__line" = "$__delim" ] && break if [ $__more = 1 ]; then __value="$__value"$'\n'"$__line"; else __value="$__line"; __more=1; fi done elif [[ $__line =~ $__gitfor_re_kv ]]; then __key="#{BASH_REMATCH[1]}"; __value="#{BASH_REMATCH[2]}" else continue fi "$2" "$__key" "$__value" "#{3:-}" done < "$1" } __gitfor_set_output() { [ -n "$3" ] || return 0; export "GITFOR_STEPOUT_#{3}__#{1//-/_}=$2"; } __gitfor_set_env() { case "$1" in *-*|__gitfor*|BASH_ENV|ENV|BASH_FUNC_*|SHELLOPTS|BASHOPTS|PS4|IFS|LD_*|DYLD_*|NODE_OPTIONS|PYTHONSTARTUP|PYTHONPATH|PYTHONHOME|PERL5OPT|PERL5LIB|RUBYOPT|GIT_*|GITFOR_*|GITHUB_*|ACTIONS_*|RUNNER_*|CI|HOME|PATH|SHELL|PROMPT_COMMAND|CDPATH|GLOBIGNORE|BASHPID|UID|EUID) echo "Ignoring reserved environment variable $1 written to GITHUB_ENV"; return 0 ;; esac export "$1=$2" } __gitfor_reset_files() { : > "$__gitfor_tmp/output"; : > "$__gitfor_tmp/env"; : > "$__gitfor_tmp/path"; } __gitfor_after_step() { local __l __o=success __c=success __gitfor_read_kv "$__gitfor_tmp/env" __gitfor_set_env if [ -s "$__gitfor_tmp/path" ]; then while IFS= read -r __l || [ -n "$__l" ]; do if [ -n "$__l" ]; then PATH="$__l:$PATH"; fi; done < "$__gitfor_tmp/path" export PATH fi if [ -n "$1" ]; then __gitfor_read_kv "$__gitfor_tmp/output" __gitfor_set_output "$1" if [ "$2" -ne 0 ]; then __o=failure; [ "$3" = 1 ] || __c=failure; fi export "GITFOR_STEPOUTCOME_$1=$__o" "GITFOR_STEPCONCLUSION_$1=$__c" fi __gitfor_reset_files } __gitfor_sha256() { if command -v sha256sum >/dev/null 2>&1; then sha256sum "$@"; else shasum -a 256 "$@"; fi; } __gitfor_hash_files() ( cd "#{GITHUB_WORKSPACE:-.}" || exit 0 shopt -s globstar nullglob dotglob 2>/dev/null __list=$(for __p in "$@"; do for __f in $__p; do if [ -f "$__f" ]; then printf '%s\n' "$__f"; fi; done; done | sort -u) [ -n "$__list" ] || exit 0 printf '%s\n' "$__list" | while IFS= read -r __f; do __gitfor_sha256 "$__f" | cut -d' ' -f1; done | __gitfor_sha256 | cut -d' ' -f1 ) export -f __gitfor_sha256 __gitfor_hash_files __gitfor_urlenc() { printf '%s' "$1" | od -An -v -tx1 | tr -d ' \n' | sed 's/../%&/g'; } __gitfor_header() ( shopt -s nocasematch __v="" while IFS= read -r __l; do __l="#{__l%$'\r'}"; if [[ $__l == "$2: "* ]]; then __v="#{__l#*: }"; fi; done < "$1" printf '%s' "$__v" ) __gitfor_tar_safe() { local __listing __listing=$(tar -tzf "$1") || return 1 if printf '%s\n' "$__listing" | grep -Eq '^/|(^|/)\.\.(/|$)'; then return 1; fi return 0 } __gitfor_cache_extract() { local __stage="$__gitfor_tmp/cache-stage" __part __root __ok=0 __outer __outer=$(tar -tzf "$1" 2>/dev/null) || return 1 if printf '%s\n' "$__outer" | grep -Evq '^(w|h)\.tgz$'; then echo "Cache archive has an unexpected layout"; return 1; fi rm -rf "$__stage" && mkdir -p "$__stage" || return 1 tar -xzf "$1" -C "$__stage" --no-same-owner || { rm -rf "$__stage"; return 1; } for __part in w h; do [ -f "$__stage/$__part.tgz" ] || continue if [ $__part = w ]; then __root="$GITHUB_WORKSPACE"; else __root="$HOME"; fi if ! __gitfor_tar_safe "$__stage/$__part.tgz"; then echo "Cache archive contains unsafe entries"; __ok=1; break; fi tar -xzf "$__stage/$__part.tgz" -C "$__root" --no-same-owner || { __ok=1; break; } done rm -rf "$__stage" return $__ok } __gitfor_cache_restore() { local __id="$1" __key="$2" __version="$3" __query __r __hit __matched __headers="$__gitfor_tmp/cache-headers" shift 3 __query="key=$(__gitfor_urlenc "$__key")&version=$__version" for __r in "$@"; do [ -n "$__r" ] && __query="$__query&restore=$(__gitfor_urlenc "$__r")"; done rm -f "$__gitfor_tmp/cache.tgz" "$__headers" if ! curl -fsS -H "Authorization: Bearer $GITFOR_JOB_TOKEN" -D "$__headers" -o "$__gitfor_tmp/cache.tgz" \ "$GITFOR_API_URL/api/actions/cache?$__query" 2>/dev/null; then echo "Cache not found for key: $__key" __gitfor_set_output cache-hit false "$__id" return 1 fi __hit=$(__gitfor_header "$__headers" x-gitfor-cache-hit) __matched=$(__gitfor_header "$__headers" x-gitfor-cache-key) __matched=$(printf '%b' "#{__matched//%/\\x}") if ! __gitfor_cache_extract "$__gitfor_tmp/cache.tgz"; then rm -f "$__gitfor_tmp/cache.tgz" echo "Failed to extract the cache archive" __gitfor_set_output cache-hit false "$__id" return 1 fi rm -f "$__gitfor_tmp/cache.tgz" echo "Cache restored from key: $__matched" __gitfor_set_output cache-matched-key "$__matched" "$__id" if [ "$__hit" = exact ]; then __gitfor_set_output cache-hit true "$__id"; return 0; fi __gitfor_set_output cache-hit false "$__id" return 1 } __gitfor_cache_save() { local __key="$1" __version="$2" __archive="$__gitfor_tmp/cache-save.tgz" __stage="$__gitfor_tmp/cache-save" __p __code __sum local __work=() __home=() __parts=() shift 2 for __p in "$@"; do case "$__p" in "~") __home+=(.) ;; "~/"*) __home+=("#{__p#"~/"}") ;; /*) echo "Cache not saved: absolute path $__p is not supported (use a workspace or ~ path)"; return 0 ;; *) __work+=("$__p") ;; esac done for __p in "#{__work[@]}" "#{__home[@]}"; do case "/$__p/" in */../*) echo "Cache not saved: path $__p must not contain '..'"; return 0 ;; esac done rm -rf "$__stage" && mkdir -p "$__stage" || return 0 if [ #{#__work[@]} -gt 0 ]; then tar -czf "$__stage/w.tgz" -C "$GITHUB_WORKSPACE" -- "#{__work[@]}" 2>/dev/null || { rm -rf "$__stage"; echo "Cache not saved: some cached paths do not exist"; return 0; } __parts+=(w.tgz) fi if [ #{#__home[@]} -gt 0 ]; then tar -czf "$__stage/h.tgz" -C "$HOME" -- "#{__home[@]}" 2>/dev/null || { rm -rf "$__stage"; echo "Cache not saved: some cached paths do not exist"; return 0; } __parts+=(h.tgz) fi tar -czf "$__archive" -C "$__stage" -- "#{__parts[@]}" || { rm -rf "$__stage"; return 0; } rm -rf "$__stage" __sum=$(sha256sum "$__archive" 2>/dev/null || shasum -a 256 "$__archive" 2>/dev/null) || { rm -f "$__archive"; echo "Cache not saved: sha256sum is not available"; return 0; } __code=$(curl -sS -o /dev/null -w '%{http_code}' -H "Authorization: Bearer $GITFOR_JOB_TOKEN" \ -H 'content-type: application/gzip' -H "x-gitfor-content-sha256: #{__sum%% *}" -T "$__archive" \ "$GITFOR_API_URL/api/actions/cache?key=$(__gitfor_urlenc "$__key")&version=$__version") || __code=000 rm -f "$__archive" case "$__code" in 2*) echo "Cache saved with key: $__key" ;; 409) echo "Cache entry for key $__key already exists; not saving" ;; 413) echo "Cache not saved: the archive exceeds the cache size limit" ;; *) echo "Cache not saved (HTTP $__code)" ;; esac }`; /** Written with `#{` for bash `${` so the template literal does not interpolate. */ export const RUNTIME_FUNCTIONS = SOURCE.replaceAll("#{", "${");