-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathinstall.sh
More file actions
executable file
·283 lines (255 loc) · 12.4 KB
/
Copy pathinstall.sh
File metadata and controls
executable file
·283 lines (255 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#!/bin/sh
# install.sh — POSIX install for TestAtlas. Pin: VERSION below is rewritten at
# release time by .github/workflows/release.yml (sed step). TARBALL_SHA256 is
# likewise rewritten by the post-publish step. The literal "REPLACE_AT_RELEASE"
# sentinel triggers placeholder mode (no checksum verification, dev install).
#
# Plan 07-02 (INSTALL-02). POSIX `/bin/sh` only — NO bashisms.
# shellcheck shell=sh
#
# TODO(07-05): list of intentionally disabled shellcheck codes lives in
# .shellcheckrc; document the reasoning in CONTRIBUTING.md.
set -eu
VERSION="2.0.10"
TARBALL_SHA256="387bf327e3c1acfae8c6631f2b6c555b451567fb8db1973f40bbb0373096dc40"
TARBALL_URL="https://registry.npmjs.org/@webventures/testatlas/-/testatlas-${VERSION}.tgz"
GITHUB_RELEASE_URL="https://github.com/CryptVenture/TestAtlas/releases/download/v${VERSION}/testatlas-${VERSION}.tgz"
_log() { printf '[testatlas] %s\n' "$*"; }
_err() { printf '[testatlas:error] %s\n' "$*" >&2; }
# POSIX banner — mirrors scripts/lib/banner.js (BANNER_UNICODE_LINES /
# BANNER_ASCII_LINES + TAGLINE + version line). Self-gates on NO_COLOR /
# FORCE_COLOR=0 / non-TTY (no ANSI) and NO_UNICODE (`#` fallback art).
_print_banner() {
_m=""; _c=""; _d=""; _r=""
if [ -z "${NO_COLOR-}" ] && [ "${FORCE_COLOR-}" != "0" ] && [ -t 1 ]; then
_m=$(printf '\033[35m'); _c=$(printf '\033[36m')
_d=$(printf '\033[2m'); _r=$(printf '\033[0m')
fi
if [ -z "${NO_UNICODE-}" ]; then
_b1=" ████████ ███████ ███████ ████████ █████ ████████ ██ █████ ███████"
_b2=" ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ "
_b3=" ██ █████ ███████ ██ ███████ ██ ██ ███████ ███████"
_b4=" ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██"
_b5=" ██ ███████ ███████ ██ ██ ██ ██ ███████ ██ ██ ███████"
else
_b1=" ######## ####### ####### ######## ##### ######## ## ##### #######"
_b2=" ## ## ## ## ## ## ## ## ## ## ## "
_b3=" ## ##### ####### ## ####### ## ## ####### #######"
_b4=" ## ## ## ## ## ## ## ## ## ## ##"
_b5=" ## ####### ####### ## ## ## ## ####### ## ## #######"
fi
printf '%s%s%s\n' "$_m" " " "$_r"
for _line in "$_b1" "$_b2" "$_b3" "$_b4" "$_b5"; do
printf '%s%s%s\n' "$_m" "$_line" "$_r"
done
printf '\n%sAgent-agnostic AI product testing & quality intelligence framework%s\n\n' "$_c" "$_r"
printf '%sv%s • https://github.com/CryptVenture/TestAtlas%s\n\n' "$_d" "$VERSION" "$_r"
}
_require_node() {
if ! command -v node >/dev/null 2>&1; then
_err "Node.js not found. Install Node >=20.11 first:"
_err " macOS: brew install node"
_err " Linux: https://nodejs.org/ (or use nvm: https://github.com/nvm-sh/nvm)"
_err " Windows: winget install OpenJS.NodeJS"
exit 1
fi
NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]")
if [ "$NODE_MAJOR" -lt 20 ]; then
_err "Node.js >=20.11 required (found $(node -v)). Upgrade and retry."
exit 1
fi
}
_download() {
url="$1"
dst="$2"
if command -v curl >/dev/null 2>&1; then
curl -fsSL "$url" -o "$dst"
elif command -v wget >/dev/null 2>&1; then
wget -q "$url" -O "$dst"
else
_err "Neither curl nor wget found. Install one and retry."
exit 1
fi
}
_verify_checksum() {
file="$1"
expected="$2"
if [ "$expected" = "REPLACE_AT_RELEASE" ]; then
_log "Checksum is placeholder (dev install). Skipping verification."
return 0
fi
if [ "${TESTATLAS_SKIP_CHECKSUM:-0}" = "1" ]; then
_log "TESTATLAS_SKIP_CHECKSUM=1 set; skipping checksum verification."
return 0
fi
if command -v shasum >/dev/null 2>&1; then
printf '%s %s\n' "$expected" "$file" | shasum -a 256 -c -
elif command -v sha256sum >/dev/null 2>&1; then
printf '%s %s\n' "$expected" "$file" | sha256sum -c -
else
_err "Neither shasum nor sha256sum found. Cannot verify checksum."
_err "Re-run with TESTATLAS_SKIP_CHECKSUM=1 to bypass (NOT recommended)."
exit 1
fi
}
# Plan 07-04 (UPDATE-07). Opt-in cosign attestation verify. Gated on the
# TESTATLAS_VERIFY_SIGNATURE=1 env var (the npx CLI's --verify-signature flag
# bridges to this). Default install path skips entirely (checksum-only).
#
# Without cosign on PATH AND env=1: actionable error pointing to the install
# guide, exit 1. The full docs/SIGNING.md lives in Plan 07-05.
_verify_signature_if_enabled() {
tarball="$1"
if [ "${TESTATLAS_VERIFY_SIGNATURE:-0}" != "1" ]; then
return 0
fi
if ! command -v cosign >/dev/null 2>&1; then
_err "cosign not found on PATH but --verify-signature requested."
_err "Install cosign: https://docs.sigstore.dev/cosign/installation/"
exit 1
fi
bundle="${tarball}.sigstore.json"
if [ ! -f "$bundle" ]; then
_log "Downloading cosign bundle from ${TARBALL_URL}.sigstore.json"
if ! _download "${TARBALL_URL}.sigstore.json" "$bundle"; then
_err "Could not download cosign attestation bundle from ${TARBALL_URL}.sigstore.json"
exit 1
fi
fi
_log "Verifying cosign attestation"
cosign verify-blob-attestation \
--bundle "$bundle" \
--new-bundle-format \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com" \
--certificate-identity-regexp="^https://github.com/CryptVenture/TestAtlas/.github/workflows/release.yml.*" \
"$tarball"
}
_main() {
# Forward all extra args to `node install.js` verbatim; detect --global
# so we can default the positional target to $HOME instead of $PWD.
INSTALL_FLAGS="$*"
GLOBAL_MODE=0
case " $INSTALL_FLAGS " in *" --global "*) GLOBAL_MODE=1 ;; esac
_print_banner
_log "Installing TestAtlas v${VERSION}${GLOBAL_MODE:+ (global mode)}"
_require_node
TMP=$(mktemp -d 2>/dev/null || mktemp -d -t testatlas)
trap 'rm -rf "$TMP"' EXIT
TARBALL="${TMP}/testatlas.tgz"
if [ -n "${_TESTATLAS_TARBALL_OVERRIDE:-}" ]; then
# Test hook: bypass network fetch and copy the local tarball.
# Used by Plan 07-02's CI smoke + node:test integration tests; not a
# public feature.
_log "Using local tarball override: ${_TESTATLAS_TARBALL_OVERRIDE}"
cp "${_TESTATLAS_TARBALL_OVERRIDE}" "$TARBALL"
# Override implies the caller takes responsibility for the file
# (test fixture or local pnpm-pack output). The hardcoded
# TARBALL_SHA256 above is the SHA of the official published npm
# tarball at release time — a locally-packed tarball cannot match
# it because tar metadata + compression bits differ. Force the
# checksum-skip path so `_verify_checksum` still runs (and emits
# the dev-mode "placeholder" / "skipping" log line tests assert
# on) but doesn't reject the local file. Signature verification
# would also reject the local file (sigstore bundle is keyed to
# the published tarball), so it stays opt-in via
# TESTATLAS_VERIFY_SIGNATURE — when the user pins that env var
# alongside the override, they're explicitly asking for a
# signature check that will fail by design.
TESTATLAS_SKIP_CHECKSUM=1
export TESTATLAS_SKIP_CHECKSUM
else
_log "Downloading from ${TARBALL_URL}"
if ! _download "$TARBALL_URL" "$TARBALL"; then
_log "npm registry fetch failed; falling back to GitHub Releases."
_download "$GITHUB_RELEASE_URL" "$TARBALL"
fi
fi
_verify_checksum "$TARBALL" "$TARBALL_SHA256"
_verify_signature_if_enabled "$TARBALL"
_log "Extracting and running install"
(cd "$TMP" && tar -xzf testatlas.tgz)
# npm tarball top-level dir is `package/`.
# Read VERSION from the extracted package.json so dev-smoke runs
# report the actual tarball version instead of the install.sh
# hardcoded constant. Release-time sed still rewrites VERSION above
# so the network-fetch URLs stay correct, but the runtime log
# tracks what we actually unpacked.
if [ -f "${TMP}/package/package.json" ]; then
UNPACKED_VERSION=$(node -p "require('${TMP}/package/package.json').version")
if [ "$UNPACKED_VERSION" != "$VERSION" ]; then
_log "Unpacked tarball is v${UNPACKED_VERSION} (install.sh pin: v${VERSION})"
fi
fi
# The npm tarball ships SOURCE only; runtime deps (commander, ajv,
# ajv-formats, semver) are NOT bundled. Resolve them inside the
# extracted dir before invoking install.js. `--omit=dev --no-audit
# --no-fund --silent` keeps the install lean (~3 packages) and
# quiet. We require `npm` to be on PATH (it ships with Node).
if ! command -v npm >/dev/null 2>&1; then
_err "npm not found on PATH (expected — it ships with Node)."
_err "Reinstall Node.js or fix PATH and retry."
exit 1
fi
_log "Resolving runtime dependencies"
(cd "${TMP}/package" && npm install --omit=dev --no-audit --no-fund --silent)
# In global mode, `install.js` defaults to $HOME when no positional target
# is supplied. Project-local mode keeps the historical `${TARGET:-$PWD}`
# default so existing CI smokes don't regress.
if [ "$GLOBAL_MODE" = "1" ]; then
# shellcheck disable=SC2086
node "${TMP}/package/install.js" $INSTALL_FLAGS
else
# shellcheck disable=SC2086
node "${TMP}/package/install.js" "${TARGET:-$PWD}" $INSTALL_FLAGS
fi
if [ "$GLOBAL_MODE" = "1" ]; then
_log "Done (global). Adapter command files are now in your user home."
else
_log "Done. Run your agent's bootstrap (e.g. /atlas:bootstrap) to start."
fi
}
_print_usage() {
_print_banner
cat <<'EOF'
Usage: install.sh [OPTIONS]
Install the TestAtlas suite into the current repo (or --target <dir>).
Options:
-h, --help Show this help and exit.
--dry-run Print resolved tarball URL + expected SHA, do not install.
--global Install adapter command files into user-home (~/.claude/, etc.).
--target <dir> Target directory (default: $PWD; $HOME with --global).
--force Overwrite an existing .testatlas/ workspace.
--verify-signature Verify cosign attestation (requires cosign on PATH).
Environment variables:
TESTATLAS_VERIFY_SIGNATURE=1 Verify cosign attestation (requires cosign).
TESTATLAS_SKIP_CHECKSUM=1 Skip SHA-256 checksum verification (NOT recommended).
_TESTATLAS_TARBALL_OVERRIDE=<path> Local tarball override (test hook only).
Examples:
curl -fsSL https://raw.githubusercontent.com/CryptVenture/TestAtlas/main/install.sh | sh
curl -fsSL https://raw.githubusercontent.com/CryptVenture/TestAtlas/main/install.sh | sh -s -- --global
/bin/sh install.sh --dry-run
EOF
}
# Short-circuit dispatch — runs BEFORE _main so --help / --dry-run never trigger
# the installer's network or filesystem side effects.
#
# Matching strategy:
# - `case "${1:-}"` checks $1 only for -h / --help (matches GNU help convention,
# where --help is expected to be the leading or sole argument).
# - `case " $* "` scans ALL args (space-padded) for --dry-run so it works with
# `sh install.sh --global --dry-run` as well as `sh install.sh --dry-run`.
case "${1:-}" in
-h|--help) _print_usage; exit 0 ;;
esac
case " $* " in
*" --dry-run "*)
_log "Dry-run: tarball=${TARBALL_URL} expected_sha=${TARBALL_SHA256}"
_log "Dry-run: github_fallback=${GITHUB_RELEASE_URL}"
_log "Dry-run: VERSION=${VERSION}"
_log "Dry-run: no install performed."
exit 0 ;;
esac
# CRITICAL: this MUST be the LAST line of the file. Partial-pipe protection:
# if `curl | sh` is interrupted before this point, sh reaches EOF without ever
# calling _main — nothing runs.
_main "$@"