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.
- package/README.md +13 -0
- package/dist/ci-local/index.d.mts +5 -0
- package/dist/ci-local/index.mjs +79 -0
- package/dist/ci-local/parse.d.mts +3 -0
- package/dist/ci-local/parse.mjs +43 -0
- package/dist/ci-local/types.d.mts +41 -0
- package/dist/ci-local/types.mjs +1 -0
- package/dist/cli/index.mjs +5 -0
- package/dist/cli/parse.d.mts +1 -1
- package/dist/cli/parse.mjs +5 -0
- package/dist/cli/usage.d.mts +1 -1
- package/dist/cli/usage.mjs +10 -0
- package/dist/coverage-transport/constants.d.mts +3 -0
- package/dist/coverage-transport/constants.mjs +11 -0
- package/dist/coverage-transport/control.d.mts +47 -0
- package/dist/coverage-transport/control.mjs +112 -0
- package/dist/coverage-transport/http.d.mts +6 -0
- package/dist/coverage-transport/http.mjs +99 -0
- package/dist/coverage-transport/index.d.mts +6 -0
- package/dist/coverage-transport/index.mjs +6 -0
- package/dist/coverage-transport/lib.d.mts +17 -0
- package/dist/coverage-transport/lib.mjs +84 -0
- package/dist/coverage-transport/outcome.d.mts +29 -0
- package/dist/coverage-transport/outcome.mjs +70 -0
- package/dist/coverage-transport/presign.d.mts +22 -0
- package/dist/coverage-transport/presign.mjs +51 -0
- package/dist/coverage-transport/vitest-blob-transport.d.mts +5 -0
- package/dist/coverage-transport/vitest-blob-transport.mjs +129 -0
- package/dist/dockerfile-parse/index.d.mts +4 -0
- package/dist/dockerfile-parse/index.mjs +2 -0
- package/dist/dockerfile-parse/prewarm-ports.d.mts +12 -0
- package/dist/dockerfile-parse/prewarm-ports.mjs +59 -0
- package/dist/dockerfile-parse/runtime-cmd.d.mts +11 -0
- package/dist/dockerfile-parse/runtime-cmd.mjs +142 -0
- package/dist/dockerfile-parse/stages.d.mts +7 -0
- package/dist/dockerfile-parse/stages.mjs +18 -0
- package/dist/gha-pr-checkpoint/index.d.mts +58 -0
- package/dist/gha-pr-checkpoint/index.mjs +100 -0
- package/dist/gha-rate-limit/index.d.mts +15 -0
- package/dist/gha-rate-limit/index.mjs +69 -0
- package/dist/index.d.mts +15 -0
- package/dist/index.mjs +8 -0
- package/dist/pnpm-deploy/index.d.mts +6 -0
- package/dist/pnpm-deploy/index.mjs +3 -0
- package/dist/pnpm-deploy/normalize.d.mts +15 -0
- package/dist/pnpm-deploy/normalize.mjs +64 -0
- package/dist/pnpm-deploy/prune.d.mts +14 -0
- package/dist/pnpm-deploy/prune.mjs +48 -0
- package/dist/pnpm-deploy/restore.d.mts +31 -0
- package/dist/pnpm-deploy/restore.mjs +109 -0
- package/dist/scc-complexity/index.d.mts +19 -0
- package/dist/scc-complexity/index.mjs +91 -0
- package/dist/transient-retry/decision-evaluator.d.mts +2 -0
- package/dist/transient-retry/decision-evaluator.mjs +6 -0
- package/dist/transient-retry/types.d.mts +3 -3
- package/dist/workspace-gates/dependabot.d.mts +3 -0
- package/dist/workspace-gates/dependabot.mjs +26 -0
- package/dist/workspace-gates/exclude-registry.d.mts +3 -0
- package/dist/workspace-gates/exclude-registry.mjs +30 -0
- package/dist/workspace-gates/first-party-graph.d.mts +4 -0
- package/dist/workspace-gates/first-party-graph.mjs +87 -0
- package/dist/workspace-gates/index.d.mts +2 -0
- package/dist/workspace-gates/index.mjs +1 -0
- package/dist/workspace-gates/options.d.mts +27 -0
- package/dist/workspace-gates/options.mjs +15 -0
- package/dist/workspace-gates/policy.d.mts +5 -0
- package/dist/workspace-gates/policy.mjs +27 -0
- package/dist/workspace-gates/test-helpers.d.mts +21 -0
- package/dist/workspace-gates/test-helpers.mjs +69 -0
- package/dist/workspace-gates/yaml-loader.d.mts +2 -0
- package/dist/workspace-gates/yaml-loader.mjs +28 -0
- package/dist/workspace-glob.d.mts +1 -0
- package/dist/workspace-glob.mjs +46 -0
- package/package.json +44 -2
- package/scripts/gha/check-cache-size.sh +38 -0
- package/scripts/gha/clean-workspace.sh +227 -0
- package/scripts/gha/install-github-release.sh +136 -0
- package/scripts/gha/load-runner-env.sh +75 -0
- 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"
|