vouchington-tooling 0.0.14 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +13 -0
  2. package/dist/ci-local/index.d.mts +5 -0
  3. package/dist/ci-local/index.mjs +79 -0
  4. package/dist/ci-local/parse.d.mts +3 -0
  5. package/dist/ci-local/parse.mjs +43 -0
  6. package/dist/ci-local/types.d.mts +41 -0
  7. package/dist/ci-local/types.mjs +1 -0
  8. package/dist/cli/index.mjs +5 -0
  9. package/dist/cli/parse.d.mts +1 -1
  10. package/dist/cli/parse.mjs +5 -0
  11. package/dist/cli/usage.d.mts +1 -1
  12. package/dist/cli/usage.mjs +10 -0
  13. package/dist/coverage-transport/constants.d.mts +3 -0
  14. package/dist/coverage-transport/constants.mjs +11 -0
  15. package/dist/coverage-transport/control.d.mts +47 -0
  16. package/dist/coverage-transport/control.mjs +112 -0
  17. package/dist/coverage-transport/http.d.mts +6 -0
  18. package/dist/coverage-transport/http.mjs +99 -0
  19. package/dist/coverage-transport/index.d.mts +6 -0
  20. package/dist/coverage-transport/index.mjs +6 -0
  21. package/dist/coverage-transport/lib.d.mts +17 -0
  22. package/dist/coverage-transport/lib.mjs +84 -0
  23. package/dist/coverage-transport/outcome.d.mts +29 -0
  24. package/dist/coverage-transport/outcome.mjs +70 -0
  25. package/dist/coverage-transport/presign.d.mts +22 -0
  26. package/dist/coverage-transport/presign.mjs +51 -0
  27. package/dist/coverage-transport/vitest-blob-transport.d.mts +5 -0
  28. package/dist/coverage-transport/vitest-blob-transport.mjs +129 -0
  29. package/dist/dockerfile-parse/index.d.mts +4 -0
  30. package/dist/dockerfile-parse/index.mjs +2 -0
  31. package/dist/dockerfile-parse/prewarm-ports.d.mts +12 -0
  32. package/dist/dockerfile-parse/prewarm-ports.mjs +59 -0
  33. package/dist/dockerfile-parse/runtime-cmd.d.mts +11 -0
  34. package/dist/dockerfile-parse/runtime-cmd.mjs +142 -0
  35. package/dist/dockerfile-parse/stages.d.mts +7 -0
  36. package/dist/dockerfile-parse/stages.mjs +18 -0
  37. package/dist/gha-pr-checkpoint/index.d.mts +58 -0
  38. package/dist/gha-pr-checkpoint/index.mjs +100 -0
  39. package/dist/gha-rate-limit/index.d.mts +15 -0
  40. package/dist/gha-rate-limit/index.mjs +69 -0
  41. package/dist/index.d.mts +15 -0
  42. package/dist/index.mjs +8 -0
  43. package/dist/pnpm-deploy/index.d.mts +6 -0
  44. package/dist/pnpm-deploy/index.mjs +3 -0
  45. package/dist/pnpm-deploy/normalize.d.mts +15 -0
  46. package/dist/pnpm-deploy/normalize.mjs +64 -0
  47. package/dist/pnpm-deploy/prune.d.mts +14 -0
  48. package/dist/pnpm-deploy/prune.mjs +48 -0
  49. package/dist/pnpm-deploy/restore.d.mts +31 -0
  50. package/dist/pnpm-deploy/restore.mjs +109 -0
  51. package/dist/scc-complexity/index.d.mts +19 -0
  52. package/dist/scc-complexity/index.mjs +91 -0
  53. package/dist/transient-retry/decision-evaluator.d.mts +2 -0
  54. package/dist/transient-retry/decision-evaluator.mjs +6 -0
  55. package/dist/transient-retry/types.d.mts +3 -3
  56. package/dist/workspace-gates/dependabot.d.mts +3 -0
  57. package/dist/workspace-gates/dependabot.mjs +26 -0
  58. package/dist/workspace-gates/exclude-registry.d.mts +3 -0
  59. package/dist/workspace-gates/exclude-registry.mjs +30 -0
  60. package/dist/workspace-gates/first-party-graph.d.mts +4 -0
  61. package/dist/workspace-gates/first-party-graph.mjs +87 -0
  62. package/dist/workspace-gates/index.d.mts +2 -0
  63. package/dist/workspace-gates/index.mjs +1 -0
  64. package/dist/workspace-gates/options.d.mts +27 -0
  65. package/dist/workspace-gates/options.mjs +15 -0
  66. package/dist/workspace-gates/policy.d.mts +5 -0
  67. package/dist/workspace-gates/policy.mjs +27 -0
  68. package/dist/workspace-gates/test-helpers.d.mts +21 -0
  69. package/dist/workspace-gates/test-helpers.mjs +69 -0
  70. package/dist/workspace-gates/yaml-loader.d.mts +2 -0
  71. package/dist/workspace-gates/yaml-loader.mjs +28 -0
  72. package/dist/workspace-glob.d.mts +1 -0
  73. package/dist/workspace-glob.mjs +46 -0
  74. package/package.json +44 -2
  75. package/scripts/gha/check-cache-size.sh +38 -0
  76. package/scripts/gha/clean-workspace.sh +227 -0
  77. package/scripts/gha/install-github-release.sh +136 -0
  78. package/scripts/gha/load-runner-env.sh +75 -0
  79. package/scripts/gha/make-shard-matrix.sh +30 -0
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ "${#}" -ne 3 ]; then
5
+ echo "usage: check-cache-size.sh <path> <max-bytes> <label>" >&2
6
+ exit 2
7
+ fi
8
+
9
+ if [ -z "${GITHUB_OUTPUT:-}" ]; then
10
+ echo 'GITHUB_OUTPUT must be set' >&2
11
+ exit 2
12
+ fi
13
+
14
+ CACHE_PATH="$1"
15
+ CACHE_SAVE_MAX_BYTES="$2"
16
+ CACHE_LABEL="$3"
17
+
18
+ if ! [[ "$CACHE_SAVE_MAX_BYTES" =~ ^[1-9][0-9]*$ ]]; then
19
+ echo "::error::max-bytes must be a positive integer, got: '$CACHE_SAVE_MAX_BYTES'"
20
+ exit 1
21
+ fi
22
+
23
+ if [ ! -e "$CACHE_PATH" ]; then
24
+ bytes=0
25
+ echo "bytes=$bytes" >> "$GITHUB_OUTPUT"
26
+ echo "save=false" >> "$GITHUB_OUTPUT"
27
+ echo "::notice::Skipping ${CACHE_LABEL} cache save because ${CACHE_PATH} does not exist."
28
+ exit 0
29
+ fi
30
+
31
+ bytes=$(du -sk "$CACHE_PATH" | awk '{printf "%.0f", $1 * 1024}')
32
+ echo "bytes=$bytes" >> "$GITHUB_OUTPUT"
33
+ if [ "$bytes" -gt "$CACHE_SAVE_MAX_BYTES" ]; then
34
+ echo "save=false" >> "$GITHUB_OUTPUT"
35
+ echo "::notice::Skipping ${CACHE_LABEL} cache save because ${CACHE_PATH} is ${bytes} bytes (> ${CACHE_SAVE_MAX_BYTES})."
36
+ else
37
+ echo "save=true" >> "$GITHUB_OUTPUT"
38
+ fi
@@ -0,0 +1,227 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ EXTRA_KEEP="${EXTRA_KEEP:-}"
5
+ EVENT_NAME="${EVENT_NAME:-${GITHUB_EVENT_NAME:-}}"
6
+ HEAD_REPO="${HEAD_REPO:-}"
7
+ BASE_REPO="${BASE_REPO:-${GITHUB_REPOSITORY:-}}"
8
+ PRESERVE_NODE_MODULES="${PRESERVE_NODE_MODULES:-true}"
9
+ PR_AUTHOR="${PR_AUTHOR:-}"
10
+ DEEPEN="${DEEPEN:-false}"
11
+ BASE_REF="${BASE_REF:-main}"
12
+ GENERATED_REPAIR_PATHS="${GENERATED_REPAIR_PATHS:-}"
13
+
14
+ read_pull_request_field() {
15
+ local field="$1"
16
+ [ -n "${GITHUB_EVENT_PATH:-}" ] && [ -f "${GITHUB_EVENT_PATH}" ] || return 0
17
+ node --input-type=module -e '
18
+ import { readFileSync } from "node:fs"
19
+ const event = JSON.parse(readFileSync(process.argv[1], "utf8"))
20
+ const value =
21
+ process.argv[2] === "head"
22
+ ? event.pull_request?.head?.repo?.full_name
23
+ : event.pull_request?.user?.login
24
+ process.stdout.write(value == null ? "" : String(value))
25
+ ' "${GITHUB_EVENT_PATH}" "${field}" 2>/dev/null || true
26
+ }
27
+
28
+ if [ -z "${HEAD_REPO}" ]; then
29
+ HEAD_REPO="$(read_pull_request_field head)"
30
+ fi
31
+ if [ -z "${PR_AUTHOR}" ]; then
32
+ PR_AUTHOR="$(read_pull_request_field author)"
33
+ fi
34
+
35
+ restore_user_directory_write_bits() {
36
+ local path
37
+ for path in "$@"; do
38
+ if [ -e "${path}" ]; then
39
+ if find -P "${path}" -xdev -type d -exec chmod u+rwx {} +; then
40
+ continue
41
+ fi
42
+ if command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then
43
+ sudo -n find -P "${path}" -xdev -type d -exec chown -h "$(id -u):$(id -g)" {} + ||
44
+ echo "::warning::Could not reclaim directory ownership under ${path}; retrying chmod without ownership repair."
45
+ find -P "${path}" -xdev -type d -exec chmod u+rwx {} + ||
46
+ echo "::warning::Could not restore user directory write bits under ${path} after reclaiming ownership; cleanup may fail if stale directories remain locked down."
47
+ else
48
+ echo "::warning::Could not restore user directory write bits under ${path}; cleanup may fail if stale directories remain locked down."
49
+ fi
50
+ fi
51
+ done
52
+ }
53
+ restore_workspace_write_bits() {
54
+ if command -v sudo >/dev/null 2>&1 && sudo -n true 2>/dev/null; then
55
+ sudo -n find -P . -xdev -path ./.git -prune -o -type d -exec chown -h "$(id -u):$(id -g)" {} +
56
+ fi
57
+ find -P . -xdev -path ./.git -prune -o -type d -exec chmod u+rwx {} +
58
+ }
59
+
60
+ workspace_sentinel_key() {
61
+ if command -v sha256sum >/dev/null 2>&1; then
62
+ sha256sum | awk '{print $1}'
63
+ return
64
+ fi
65
+ shasum -a 256 | awk '{print $1}'
66
+ }
67
+
68
+ if ! git reset --hard HEAD; then
69
+ echo "::warning::git reset --hard failed; reclaiming workspace write bits and retrying once."
70
+ restore_workspace_write_bits ||
71
+ echo "::warning::Could not restore workspace write bits completely; retrying git reset with the repaired subset."
72
+ git reset --hard HEAD
73
+ fi
74
+ excludes=()
75
+ # Trust-gate: a fork pull request is the only untrusted case.
76
+ # `HEAD_REPO` is non-empty only on `pull_request` events; a
77
+ # mismatch with `BASE_REPO` means the PR came from a fork. Every
78
+ # other event (push, schedule, workflow_dispatch,
79
+ # pull_request_target, same-repo pull_request, future events like
80
+ # merge_group) is trusted and preserves `node_modules` only when
81
+ # PRESERVE_NODE_MODULES is true, plus
82
+ # `extra-keep` so self-hosted installs can relink from the local
83
+ # pnpm store without downloading packages again. Fork PRs get a full clean so
84
+ # an untrusted contributor cannot leave modified package binaries
85
+ # that a later trusted run executes via `pnpm exec` with elevated
86
+ # permissions.
87
+ # Note: in reusable workflows triggered via `workflow_call`,
88
+ # `github.event_name` evaluates against the *caller's* event — so
89
+ # the trust-gate applies uniformly to direct and reusable workflows.
90
+ is_fork_pr=false
91
+ if [ "${EVENT_NAME}" = "pull_request" ] && {
92
+ [ -z "${HEAD_REPO}" ] || [ "${HEAD_REPO}" != "${BASE_REPO}" ]
93
+ }; then
94
+ is_fork_pr=true
95
+ fi
96
+ is_dependabot_pr=false
97
+ if [ "${EVENT_NAME}" = "pull_request" ] && [ "${PR_AUTHOR:-}" = "dependabot[bot]" ]; then
98
+ is_dependabot_pr=true
99
+ fi
100
+ # Contamination sentinel: fork PRs share the persistent runner's
101
+ # workspace with subsequent trusted jobs. A fork PR's code runs
102
+ # after the initial full clean and can replace binaries in
103
+ # node_modules. The sentinel (written outside the workspace so
104
+ # git clean cannot remove it) tells the next run to force-clean
105
+ # regardless of its trust level, then removes itself. Keyed on the
106
+ # workspace path so multiple repos on the same runner don't
107
+ # interfere.
108
+ _ws_key="$(printf '%s' "${GITHUB_WORKSPACE:-$(pwd)}" | workspace_sentinel_key)"
109
+ SENTINEL="${RUNNER_TOOL_CACHE:-${HOME}}/.cw-fork-pr-${_ws_key}"
110
+ if [ -f "${SENTINEL}" ]; then
111
+ rm -f "${SENTINEL}"
112
+ is_fork_pr=true
113
+ fi
114
+ if [ "${is_fork_pr}" = "false" ] && [ "${is_dependabot_pr}" = "false" ]; then
115
+ if [ "${PRESERVE_NODE_MODULES:-true}" = "true" ]; then
116
+ excludes+=(-e node_modules)
117
+ fi
118
+ excludes+=(-e .cache)
119
+ if [ -n "${EXTRA_KEEP}" ]; then
120
+ # Disable globbing so patterns like `dist/*` are passed literally
121
+ # to `git clean -e` rather than being expanded by the shell
122
+ # against the current working tree.
123
+ set -f
124
+ for pattern in ${EXTRA_KEEP}; do
125
+ excludes+=(-e "${pattern}")
126
+ done
127
+ set +f
128
+ fi
129
+ fi
130
+ generated_repair_paths=()
131
+ if [ -n "${RUNNER_TEMP:-}" ]; then
132
+ generated_repair_paths+=("${RUNNER_TEMP}/wrangler-logs")
133
+ fi
134
+ if [ -n "${GENERATED_REPAIR_PATHS}" ]; then
135
+ set -f
136
+ for pattern in ${GENERATED_REPAIR_PATHS}; do
137
+ generated_repair_paths+=("${pattern}")
138
+ done
139
+ set +f
140
+ fi
141
+ while IFS= read -r path; do
142
+ generated_repair_paths+=("${path}")
143
+ done < <(find -P . -xdev -path ./.git -prune -o -type d -name node_modules -print -prune)
144
+ if [ "${#generated_repair_paths[@]}" -gt 0 ]; then
145
+ restore_user_directory_write_bits "${generated_repair_paths[@]}"
146
+ fi
147
+ # Bash 3.2 on macOS treats an empty array expansion as unbound under
148
+ # `set -u`; use the safe expansion form so fork-PR runs can
149
+ # full-clean with no excludes.
150
+ if ! git clean -ffdx "${excludes[@]+"${excludes[@]}"}"; then
151
+ echo "::warning::git clean failed after targeted repair; reclaiming workspace write bits and retrying once."
152
+ restore_workspace_write_bits ||
153
+ echo "::warning::Could not restore workspace write bits completely; retrying git clean with the repaired subset."
154
+ git clean -ffdx "${excludes[@]+"${excludes[@]}"}"
155
+ fi
156
+ # Runs that preserve node_modules retain them for install speed.
157
+ # Drop TypeScript incremental build info inside those preserved trees
158
+ # so stale virtual-store paths do not survive dependency graph changes.
159
+ while IFS= read -r cache_dir; do
160
+ rm -rf "${cache_dir}/tsbuildinfo"* "${cache_dir}"/*.tsbuildinfo
161
+ done < <(find -P . -xdev -path ./.git -prune -o -type d -path '*/node_modules/.cache' -print 2>/dev/null || true)
162
+ # Write the sentinel after the fork PR's full clean. The next run on
163
+ # this persistent runner will see it, force-clean, and remove it. Use
164
+ # the raw event test (not the sentinel-overridden is_fork_pr) so a
165
+ # trusted job that consumed the sentinel does not re-write it.
166
+ if [ "${EVENT_NAME}" = "pull_request" ] && {
167
+ [ -z "${HEAD_REPO}" ] || [ "${HEAD_REPO}" != "${BASE_REPO}" ]
168
+ }; then
169
+ touch "${SENTINEL}" || echo "::warning::Could not write contamination sentinel to ${SENTINEL}; the next trusted run on this persistent runner may not force-clean node_modules."
170
+ fi
171
+ if [ "${DEEPEN}" = "true" ]; then
172
+ # Self-hosted runners occasionally see transient libcurl Recv
173
+ # failures or stuck git-remote-https children against github.com.
174
+ # Bound each fetch attempt so the retry loop gets control back
175
+ # before the composite action's step timeout kills the whole step.
176
+ fetch_git_config=(-c http.lowSpeedLimit=1000 -c http.lowSpeedTime=30)
177
+ if [ -n "${GITHUB_TOKEN:-}" ]; then
178
+ fetch_git_config+=(
179
+ -c 'credential.helper=!f() { printf "username=x-access-token\npassword=%s\n" "${GITHUB_TOKEN}"; }; f'
180
+ )
181
+ fi
182
+ git_fetch_once() {
183
+ local timeout_seconds=25
184
+ if command -v timeout >/dev/null 2>&1; then
185
+ timeout --kill-after=5s "${timeout_seconds}s" git "${fetch_git_config[@]}" fetch "$@"
186
+ return $?
187
+ fi
188
+ if command -v perl >/dev/null 2>&1; then
189
+ perl -e 'my $t = shift @ARGV; my $pid = fork(); if (!defined $pid) { exit 125; } if ($pid == 0) { setpgrp(0, 0); exec @ARGV; exit 127; } local $SIG{ALRM} = sub { kill(15, -$pid); for (1..50) { last if waitpid($pid, 1) == $pid; select undef, undef, undef, 0.1; } kill(9, -$pid); exit 124; }; alarm $t; waitpid($pid, 0); my $status = $?; exit($status & 127 ? 128 + ($status & 127) : $status >> 8);' "${timeout_seconds}" git "${fetch_git_config[@]}" fetch "$@"
190
+ return $?
191
+ fi
192
+ git "${fetch_git_config[@]}" fetch "$@"
193
+ }
194
+ git_fetch_with_retry() {
195
+ local attempt
196
+ for attempt in 1 2 3; do
197
+ local status
198
+ if git_fetch_once "$@"; then
199
+ return 0
200
+ else
201
+ status=$?
202
+ fi
203
+ if [ "${status}" -eq 124 ] || [ "${status}" -eq 137 ]; then
204
+ echo "::warning::git fetch $* timed out on attempt ${attempt}"
205
+ fi
206
+ if [ "${attempt}" -lt 3 ]; then
207
+ local delay=5
208
+ [ "${attempt}" -eq 2 ] && delay=15
209
+ echo "::warning::git fetch $* failed on attempt ${attempt}; retrying in ${delay}s"
210
+ sleep "${delay}"
211
+ fi
212
+ done
213
+ echo "::warning::git fetch $* failed after 3 attempts"
214
+ return 1
215
+ }
216
+ if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
217
+ if ! git_fetch_with_retry --unshallow --no-tags origin; then
218
+ echo "::warning::git fetch --unshallow exhausted retries; falling back to a regular fetch"
219
+ git_fetch_with_retry --no-tags --prune origin
220
+ if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
221
+ echo "::error::Repository is still shallow after fallback fetch. Downstream git diff operations may produce incomplete results."
222
+ exit 1
223
+ fi
224
+ fi
225
+ fi
226
+ git_fetch_with_retry --no-tags origin "+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}"
227
+ fi
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ usage() {
5
+ echo "usage: install-github-release.sh --repo owner/name --version VERSION --asset TEMPLATE --bin NAME [--tag-prefix PREFIX] [--strip-components N]" >&2
6
+ }
7
+
8
+ REPO="${REPO:-}"
9
+ VERSION="${VERSION:-}"
10
+ ASSET_TEMPLATE="${ASSET_TEMPLATE:-}"
11
+ BIN_NAME="${BIN_NAME:-}"
12
+ TAG_PREFIX="${TAG_PREFIX:-}"
13
+ STRIP_COMPONENTS="${STRIP_COMPONENTS:-1}"
14
+
15
+ if [ -n "${RELEASE_OWNER:-}" ] && [ -n "${RELEASE_REPO:-}" ]; then
16
+ REPO="${RELEASE_OWNER}/${RELEASE_REPO}"
17
+ fi
18
+
19
+ while [ "${#}" -gt 0 ]; do
20
+ case "$1" in
21
+ --repo)
22
+ [ "${#}" -ge 2 ] || { usage; exit 2; }
23
+ REPO="$2"
24
+ shift 2
25
+ ;;
26
+ --version)
27
+ [ "${#}" -ge 2 ] || { usage; exit 2; }
28
+ VERSION="$2"
29
+ shift 2
30
+ ;;
31
+ --asset)
32
+ [ "${#}" -ge 2 ] || { usage; exit 2; }
33
+ ASSET_TEMPLATE="$2"
34
+ shift 2
35
+ ;;
36
+ --bin)
37
+ [ "${#}" -ge 2 ] || { usage; exit 2; }
38
+ BIN_NAME="$2"
39
+ shift 2
40
+ ;;
41
+ --tag-prefix)
42
+ [ "${#}" -ge 2 ] || { usage; exit 2; }
43
+ TAG_PREFIX="$2"
44
+ shift 2
45
+ ;;
46
+ --strip-components)
47
+ [ "${#}" -ge 2 ] || { usage; exit 2; }
48
+ STRIP_COMPONENTS="$2"
49
+ shift 2
50
+ ;;
51
+ -h | --help)
52
+ usage
53
+ exit 2
54
+ ;;
55
+ *)
56
+ usage
57
+ exit 2
58
+ ;;
59
+ esac
60
+ done
61
+
62
+ if [ -z "$REPO" ] || [ -z "$VERSION" ] || [ -z "$ASSET_TEMPLATE" ] || [ -z "$BIN_NAME" ]; then
63
+ usage
64
+ exit 2
65
+ fi
66
+
67
+ if [[ ! "$REPO" =~ ^[^/]+/[^/]+$ ]]; then
68
+ echo "install-github-release.sh: --repo must be owner/name" >&2
69
+ exit 2
70
+ fi
71
+
72
+ if ! [[ "$STRIP_COMPONENTS" =~ ^[0-9]+$ ]]; then
73
+ echo "install-github-release.sh: --strip-components must be a non-negative integer" >&2
74
+ exit 2
75
+ fi
76
+
77
+ BIN_DIR="${RUNNER_TEMP:-${HOME}/.local}/bin"
78
+ mkdir -p "$BIN_DIR"
79
+ if [ -n "${GITHUB_PATH:-}" ]; then
80
+ echo "$BIN_DIR" >> "$GITHUB_PATH"
81
+ fi
82
+
83
+ already_installed() {
84
+ local output
85
+ output="$("$BIN_DIR/$BIN_NAME" --version 2>/dev/null || true)"
86
+ printf '%s\n' "$output" | awk -v v="$VERSION" '
87
+ {
88
+ n = split($0, parts, /[^0-9A-Za-z.-]+/)
89
+ for (i = 1; i <= n; i++) if (parts[i] == v) found = 1
90
+ }
91
+ END { exit found ? 0 : 1 }
92
+ '
93
+ }
94
+
95
+ if [ -f "$BIN_DIR/$BIN_NAME" ] && already_installed; then
96
+ echo "$BIN_NAME $VERSION already installed"
97
+ exit 0
98
+ fi
99
+
100
+ if [ -n "${PLATFORM:-}" ]; then
101
+ RELEASE_PLATFORM="$PLATFORM"
102
+ else
103
+ case "$(uname)-$(uname -m)" in
104
+ Darwin-arm64) RELEASE_PLATFORM=aarch64-apple-darwin ;;
105
+ Darwin-x86_64) RELEASE_PLATFORM=x86_64-apple-darwin ;;
106
+ Linux-x86_64) RELEASE_PLATFORM=x86_64-unknown-linux-gnu ;;
107
+ Linux-aarch64) RELEASE_PLATFORM=aarch64-unknown-linux-gnu ;;
108
+ *)
109
+ echo "Unsupported platform: $(uname)-$(uname -m)" >&2
110
+ exit 1
111
+ ;;
112
+ esac
113
+ fi
114
+
115
+ asset_name="$ASSET_TEMPLATE"
116
+ asset_name="${asset_name//\{version\}/${VERSION}}"
117
+ asset_name="${asset_name//\{platform\}/${RELEASE_PLATFORM}}"
118
+ base_url="https://github.com/${REPO}/releases/download/${TAG_PREFIX}${VERSION}"
119
+ archive="${RUNNER_TEMP:-/tmp}/${BIN_NAME}-${VERSION}-${RELEASE_PLATFORM}.tar.gz"
120
+ extract_dir="${RUNNER_TEMP:-/tmp}/${BIN_NAME}-${VERSION}-${RELEASE_PLATFORM}"
121
+
122
+ rm -rf "$extract_dir"
123
+ mkdir -p "$extract_dir"
124
+ curl -fsSL -o "$archive" "${base_url}/${asset_name}"
125
+ curl -fsSL -o "${archive}.sha256" "${base_url}/${asset_name}.sha256"
126
+ expected=$(awk '{print $1}' "${archive}.sha256")
127
+ if command -v sha256sum >/dev/null 2>&1; then
128
+ echo "${expected} ${archive}" | sha256sum -c -
129
+ else
130
+ echo "${expected} ${archive}" | shasum -a 256 -c -
131
+ fi
132
+ rm "${archive}.sha256"
133
+ tar -xzf "$archive" -C "$extract_dir" --strip-components="$STRIP_COMPONENTS"
134
+ mv "$extract_dir/$BIN_NAME" "$BIN_DIR/$BIN_NAME"
135
+ chmod +x "$BIN_DIR/$BIN_NAME"
136
+ rm -rf "$archive" "$extract_dir"
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ -z "${GITHUB_ENV:-}" ]; then
5
+ echo 'GITHUB_ENV must be set' >&2
6
+ exit 2
7
+ fi
8
+
9
+ WORKER_VAR_NAMES="${WORKER_VAR_NAMES:-}"
10
+ env_file="${RUNNER_ENV_FILE:-${HOME}/.github-actions.env}"
11
+
12
+ _write_workers_var() {
13
+ local name="$1"
14
+ local value="${2:-}"
15
+ if [ -n "$value" ]; then
16
+ value=$(printf '%s' "$value" | tr -d '\n\r')
17
+ value=$(printf '%s' "$value" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')
18
+ if printf '%s' "$value" | grep -qE '^[1-9][0-9]*%?$'; then
19
+ printf '%s=%s\n' "$name" "$value" >> "$GITHUB_ENV"
20
+ else
21
+ echo "::warning::$name='$value' is not a valid worker count (positive integer or percentage) — ignoring"
22
+ fi
23
+ fi
24
+ }
25
+
26
+ _is_worker_var() {
27
+ local candidate="$1"
28
+ local name
29
+ [ -z "$WORKER_VAR_NAMES" ] && return 1
30
+ for name in $WORKER_VAR_NAMES; do
31
+ if [ "$name" = "$candidate" ]; then
32
+ return 0
33
+ fi
34
+ done
35
+ return 1
36
+ }
37
+
38
+ if [ -n "$WORKER_VAR_NAMES" ]; then
39
+ for name in $WORKER_VAR_NAMES; do
40
+ input_name="INPUT_${name}"
41
+ _write_workers_var "$name" "${!input_name:-}"
42
+ done
43
+ fi
44
+
45
+ if [ ! -f "$env_file" ]; then
46
+ exit 0
47
+ fi
48
+
49
+ echo "::debug::Loading runner environment from ${env_file}"
50
+ while IFS='=' read -r key value; do
51
+ case "$key" in
52
+ NODE_OPTIONS)
53
+ echo "::warning::Refusing to load NODE_OPTIONS from ${env_file} — GitHub Actions blocks NODE_OPTIONS writes through GITHUB_ENV; set it in workflow env instead"
54
+ ;;
55
+ AWS_*)
56
+ echo "::warning::Refusing to load $key from ${env_file} — AWS credentials must come from aws-actions/configure-aws-credentials (OIDC)"
57
+ ;;
58
+ BASH_ENV | ENV | PATH | LD_* | DYLD_* | GIT_*)
59
+ echo "::warning::Refusing to load $key from ${env_file} — $key can hijack shell startup, the dynamic loader, PATH, or git in privileged steps; set it explicitly in workflow env if genuinely required"
60
+ ;;
61
+ *)
62
+ if _is_worker_var "$key"; then
63
+ _write_workers_var "$key" "$value"
64
+ else
65
+ printf '%s=%s\n' "$key" "$value" >> "$GITHUB_ENV"
66
+ fi
67
+ ;;
68
+ esac
69
+ done < <(
70
+ grep -vE '^[[:space:]]*(#|$)' "$env_file" \
71
+ | sed -E 's/^[[:space:]]*export[[:space:]]+//' \
72
+ | tr -d '\r' \
73
+ | grep -E '^[A-Za-z_][A-Za-z0-9_]*=' \
74
+ || true
75
+ )
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ if [ "${#}" -ne 1 ]; then
5
+ echo "usage: make-shard-matrix.sh <total>" >&2
6
+ exit 2
7
+ fi
8
+
9
+ if [ -z "${GITHUB_OUTPUT:-}" ]; then
10
+ echo 'GITHUB_OUTPUT must be set' >&2
11
+ exit 2
12
+ fi
13
+
14
+ TOTAL="$1"
15
+ if ! [[ "$TOTAL" =~ ^[1-9][0-9]*$ ]]; then
16
+ echo "::error::shard total must be a positive integer, got: '$TOTAL'"
17
+ exit 1
18
+ fi
19
+ i=1
20
+ matrix_ids=""
21
+ while [ "$i" -le "$TOTAL" ]; do
22
+ if [ -n "$matrix_ids" ]; then
23
+ matrix_ids="${matrix_ids},${i}"
24
+ else
25
+ matrix_ids="$i"
26
+ fi
27
+ i=$((i + 1))
28
+ done
29
+ echo "total=$TOTAL" >> "$GITHUB_OUTPUT"
30
+ echo "matrix=[${matrix_ids}]" >> "$GITHUB_OUTPUT"