From 5d8ffc214cc67af124866d3b7079882a830a2b95 Mon Sep 17 00:00:00 2001 From: Giuseppe Guerra Date: Tue, 26 May 2026 14:50:43 +0200 Subject: [PATCH] chore: add claude command to refresh test plugins dependencies --- .claude/commands/update-test-plugins.md | 156 +++++++ .opencode/commands/update-test-plugins.md | 1 + scripts/update-test-plugin.sh | 516 ++++++++++++++++++++++ 3 files changed, 673 insertions(+) create mode 100644 .claude/commands/update-test-plugins.md create mode 120000 .opencode/commands/update-test-plugins.md create mode 100755 scripts/update-test-plugin.sh diff --git a/.claude/commands/update-test-plugins.md b/.claude/commands/update-test-plugins.md new file mode 100644 index 000000000..215bb5411 --- /dev/null +++ b/.claude/commands/update-test-plugins.md @@ -0,0 +1,156 @@ +--- +description: Refresh deps and tooling across all test plugins in tests/ (excludes tests/act/) +argument-hint: "[plugin-name]" +allowed-tools: Bash, Read, Edit, Write, Glob, Grep, TodoWrite +--- + +You are performing a chore refresh of one or more Grafana test plugins under +`tests/`. The folder `tests/act/` is the Go test harness, NOT a plugin — +never touch it. Other folders directly under `tests/` are plugins to update. + +A helper script `scripts/update-test-plugin.sh` performs the deterministic +work. Your job is to orchestrate it, apply judgment to the non-deterministic +parts (`@grafana/*` bumps, vuln triage, build failures), enforce the exact-pin +invariant, and produce a final summary. + +# Hard invariants + +1. **Every version string in any plugin's `package.json` MUST be exact semver** + (e.g. `1.2.3`, `1.2.3-rc.1`). No `^`, `~`, `>`, `<`, `>=`, `<=`, `*`, `x`, + `||`, or whitespace ranges. This applies to `dependencies`, + `devDependencies`, `peerDependencies`, `optionalDependencies`, `overrides`, + `resolutions`, and `pnpm.overrides`. Non-registry specifiers (`file:`, + `link:`, `workspace:`, `git+`, `http(s):`, `github:`, `npm:`) are allowed + as-is. +2. **Never `git add`, `git commit`, or push**. Leave changes unstaged. +3. **Never modify `tests/act/`** in any way. + +# Step inventory (per plugin) + +Run via `./scripts/update-test-plugin.sh --step `: + +| Step | Purpose | +|----------------|--------------------------------------------------------------------| +| `node-version` | Write latest LTS major to `.nvmrc`, install via nvm | +| `pkg-manager` | Update `packageManager` field; activate via Corepack (yarn stays 1.x) | +| `cp-update` | `npx @grafana/create-plugin@latest update --force` | +| `pin-exact` | Rewrite every non-exact dep in package.json to exact + reinstall | +| `list-grafana` | Print `
\t\t` for every `@grafana/*` dep | +| `go-version` | Bump `go` directive in `go.mod` to latest stable (skips if no go.mod) | +| `go-sdk` | `go get github.com/grafana/grafana-plugin-sdk-go@latest` | +| `osv-scan` | Run `osv-scanner` and emit JSON | +| `go-tidy` | `go mod tidy` | +| `verify` | ` install`, typecheck, lint, build, plus mage if `Magefile.go` | + +# Workflow + +1. **Pick plugins.** + - If `$ARGUMENTS` is non-empty, treat it as a single plugin name. Validate + it appears in `./scripts/find-tests.sh`. If not, error out and stop. + - Otherwise, iterate over every line printed by + `./scripts/find-tests.sh` (each is `./`; strip the `./`). Skip + any entry whose name starts with `.` (e.g. `.claude`) — those are not + plugins. The helper script also enforces this defensively. + +2. **Create todos.** Use `TodoWrite` to track work as (plugin, phase) pairs. + +3. **Per plugin, run in this order** (continue to the next plugin on failure, + but record the failure for the final summary): + + 1. `node-version` + 2. `pkg-manager` + 3. `cp-update` + 4. `pin-exact` (pass 1 — clean up ranges create-plugin reintroduced) + 5. **Bump `@grafana/*` packages**: + - Run `--step list-grafana` to get the package list. + - For each ``, resolve the exact latest version: + `npm view version`. + - Install all bumps in a single command for the plugin's package manager, + always with the exact-version flag: + - npm: `npm install @ @ ... --save-exact` + - pnpm: `pnpm add @ @ ... --save-exact` + - yarn: `yarn add @ @ ... --exact` + - The package manager / install must run with the plugin's Node active. + Use: `cd tests/ && source ~/.nvm/nvm.sh && nvm use && ...` + - Respect the section: dependencies bumps use the default flag, + devDependencies bumps add `-D` (npm/pnpm) or `--dev` (yarn). + 6. `go-version` (skipped automatically if no go.mod) + 7. `go-sdk` (skipped automatically if no go.mod) + 8. **`osv-scan` and remediate**: + - Run `--step osv-scan` and parse the JSON output. + - For each vulnerability with severity `HIGH` or `CRITICAL`: + - Identify the affected package and ecosystem (npm / Go). + - If the affected package is a direct dependency, bump it to an exact + fixed version via ` add ... --save-exact` (npm) or `go get`. + - If transitive (npm), add an entry to the appropriate exact-version + override map: + - npm → top-level `overrides` + - yarn → top-level `resolutions` + - pnpm → `pnpm.overrides` + Always use a single exact version, never a range. + - If transitive (Go), bump the offending direct dep that pulls it in, + or add a `require ` line for the transitive module + to force the patched version, then `go mod tidy`. + - After remediation, re-run `osv-scan` and confirm no `HIGH`/`CRITICAL` + vulns remain. If any remain that cannot be cleanly remediated, + record them in the summary and move on. + 9. `go-tidy` + 10. `pin-exact` (pass 2 — final invariant sweep) + 11. `verify` + +4. **On `verify` failure — best-effort iterative fix.** Try multiple targeted + fixes until verify passes or you run out of plausible options. Examples: + - Re-read the error output, identify the failing package, downgrade just + that one to a known-working exact version. + - Re-run `cp-update` if config files appear inconsistent. + - Re-run `pin-exact` if any range slipped back in. + - Clean `node_modules` and lockfile fragments, then re-install. + - For mage failures, ensure `go-tidy` was run after all Go bumps. + After each attempted fix, re-run `verify`. If still failing after several + attempts, mark the plugin failed and continue. + +5. **Final invariant check.** Before reporting a plugin as green, grep its + `package.json` to confirm no value matches a non-exact pattern. Quick + sanity check: + + ``` + jq -r ' + [.dependencies, .devDependencies, .peerDependencies, .optionalDependencies, + .overrides, .resolutions, (.pnpm // {}).overrides] + | map(. // {}) | map(to_entries[]) + | .[] | select((.value | type) == "string") + | select(.value | test("^(file:|link:|portal:|workspace:|git\\+|git:|http:|https:|github:|npm:)") | not) + | select(.value | test("^[0-9]+\\.[0-9]+\\.[0-9]+([-+][0-9A-Za-z.+-]+)?$") | not) + | "\(.key) = \(.value)" + ' tests//package.json + ``` + + If anything is printed, fix it and re-run `pin-exact` + `verify`. + +6. **Summary.** Print a markdown table at the end: + + ``` + | plugin | node | pm | cp-update | pin1 | @grafana | go | osv | tidy | pin2 | verify | + |--------|------|----|-----------|------|----------|----|-----|------|------|--------| + ``` + + Use ✓ / ✗ / — (skipped/n-a) per cell. Below the table, for each ✗ cell + include a one-line cause. + +7. **Mockdata.** If every plugin reports green across the board, run + `make mockdata` to refresh `tests/act/mockdata/dist/*`. If anything failed, + skip mockdata and explain that the user should re-run after addressing the + failures. + +8. **Done.** Do not stage or commit. Just print the summary and stop. + +# Tips + +- The helper validates the plugin argument against `find-tests.sh` so you + cannot accidentally point it at `tests/act/`. +- All shell commands the helper issues run with `nvm use` inside the plugin + dir, so the right Node is active automatically. +- `pin-exact` is idempotent — safe to run repeatedly. +- Yarn 1.x is intentional (v4 migration is pending). Do NOT bump yarn to v2+. +- The plugin sources are dummy; you do not need to read or modify any plugin + source code. If a build fails, it is almost always a dependency issue. diff --git a/.opencode/commands/update-test-plugins.md b/.opencode/commands/update-test-plugins.md new file mode 120000 index 000000000..04ed425ff --- /dev/null +++ b/.opencode/commands/update-test-plugins.md @@ -0,0 +1 @@ +../../.claude/commands/update-test-plugins.md \ No newline at end of file diff --git a/scripts/update-test-plugin.sh b/scripts/update-test-plugin.sh new file mode 100755 index 000000000..fc8779f8e --- /dev/null +++ b/scripts/update-test-plugin.sh @@ -0,0 +1,516 @@ +#!/usr/bin/env bash +# +# Updates dependencies and tooling for a single test plugin under tests/. +# Designed to be invoked one step at a time by the /update-test-plugins slash +# command (or run end-to-end with --all for a non-interactive sweep). +# +# Usage: +# scripts/update-test-plugin.sh --step +# scripts/update-test-plugin.sh --all +# +# Available steps (run in this order by --all): +# node-version Update .nvmrc to latest LTS major; install via nvm +# pkg-manager Update packageManager field; activate via corepack +# cp-update Run `npx @grafana/create-plugin@latest update --force` +# pin-exact Rewrite all non-exact versions in package.json to exact +# list-grafana List @grafana/* packages in package.json (for Claude) +# go-version Bump `go` directive in go.mod to latest stable +# go-sdk Bump grafana-plugin-sdk-go to latest +# osv-scan Run osv-scanner and print JSON report +# go-tidy Run `go mod tidy` +# verify Install, typecheck, lint, build (+ mage if backend) +# +# Notes: +# - Yarn is intentionally pinned to the latest 1.x release. The v4 migration +# is pending; revisit this when ready. +# - All package.json version values are kept as exact semver to reduce +# supply-chain risk. The pin-exact step enforces this invariant. + +set -euo pipefail + +# --- bootstrap ---------------------------------------------------------------- + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +# Source nvm so node/npm/pnpm/yarn invocations pick up the right Node version. +# shellcheck disable=SC1091 +export NVM_DIR="${NVM_DIR:-$HOME/.nvm}" +if [ -s "$NVM_DIR/nvm.sh" ]; then + . "$NVM_DIR/nvm.sh" +else + echo "ERROR: nvm not found at $NVM_DIR/nvm.sh" >&2 + exit 1 +fi + +# --- arg parsing -------------------------------------------------------------- + +usage() { + sed -n '2,29p' "$0" >&2 + exit "${1:-1}" +} + +if [ "$#" -lt 1 ]; then + usage 1 +fi + +PLUGIN="$1" +shift + +STEP="" +RUN_ALL=0 + +while [ "$#" -gt 0 ]; do + case "$1" in + --step) + [ "$#" -ge 2 ] || { echo "ERROR: --step requires a value" >&2; exit 1; } + STEP="$2" + shift 2 + ;; + --all) + RUN_ALL=1 + shift + ;; + -h|--help) + usage 0 + ;; + *) + echo "ERROR: unknown argument: $1" >&2 + exit 1 + ;; + esac +done + +if [ "$RUN_ALL" -eq 0 ] && [ -z "$STEP" ]; then + echo "ERROR: must specify either --step or --all" >&2 + exit 1 +fi + +# --- validate plugin ---------------------------------------------------------- + +# Use find-tests.sh to enumerate valid plugins so we never hardcode names and +# automatically pick up new test plugins added in the future. Dotfile dirs +# (e.g. .claude/) are not plugins and are excluded here defensively. +list_plugins() { + ./scripts/find-tests.sh | sed 's|^\./||' | grep -vE '^\.' || true +} + +VALID=0 +while IFS= read -r name; do + [ -z "$name" ] && continue + if [ "$name" = "$PLUGIN" ]; then + VALID=1 + break + fi +done < <(list_plugins) + +if [ "$VALID" -ne 1 ]; then + echo "ERROR: '$PLUGIN' is not a valid test plugin." >&2 + echo "Valid plugins:" >&2 + list_plugins | sed 's/^/ /' >&2 + exit 1 +fi + +PLUGIN_DIR="$REPO_ROOT/tests/$PLUGIN" + +# --- helpers ------------------------------------------------------------------ + +log() { + printf '[%s] %s\n' "$PLUGIN" "$*" >&2 +} + +require_cmd() { + if ! command -v "$1" >/dev/null 2>&1; then + echo "ERROR: required command '$1' not found in PATH" >&2 + exit 1 + fi +} + +# Detect the package manager for the current plugin via lockfile presence. +detect_pm() { + if [ -f "$PLUGIN_DIR/yarn.lock" ]; then + echo "yarn" + elif [ -f "$PLUGIN_DIR/pnpm-lock.yaml" ]; then + echo "pnpm" + elif [ -f "$PLUGIN_DIR/package-lock.json" ]; then + echo "npm" + else + # Fall back to packageManager field if no lockfile yet. + local pm_field + pm_field="$(jq -r '.packageManager // ""' "$PLUGIN_DIR/package.json" 2>/dev/null || true)" + case "$pm_field" in + yarn@*) echo "yarn" ;; + pnpm@*) echo "pnpm" ;; + npm@*) echo "npm" ;; + *) echo "npm" ;; + esac + fi +} + +# Fetch the latest stable Go version (without the leading "go") from go.dev. +latest_go_version() { + require_cmd curl + require_cmd jq + curl -fsSL 'https://go.dev/dl/?mode=json' \ + | jq -r '[.[] | select(.stable==true)] | .[0].version' \ + | sed 's/^go//' +} + +# Fetch the latest LTS major Node version (e.g. "22" or "24"). +latest_lts_major() { + # nvm ls-remote --lts prints lines like "v22.11.0 (LTS: Jod) *" + # We grab the last entry which is the newest. + local v + v="$(nvm ls-remote --lts --no-colors 2>/dev/null | grep -E '^\s*v[0-9]+' | tail -1 | awk '{print $1}')" + if [ -z "$v" ]; then + echo "ERROR: failed to determine latest LTS Node version" >&2 + exit 1 + fi + # strip leading "v" and keep only the major + v="${v#v}" + echo "${v%%.*}" +} + +# Latest published version of an npm package on the default dist-tag. +npm_latest() { + local pkg="$1" + npm view "$pkg" version 2>/dev/null +} + +# Highest version of a package matching a constraint (e.g. "1" -> latest 1.x). +# Uses npm view + plain text parsing. Returns the last (highest) match. +npm_latest_matching() { + local pkg="$1" + local range="$2" + npm view "${pkg}@${range}" version 2>/dev/null | tail -1 | awk '{print $2}' | tr -d "'\"" +} + +# Latest package manager version, respecting our policy (yarn stays on 1.x). +latest_pm_version() { + local pm="$1" + case "$pm" in + npm|pnpm) + npm_latest "$pm" + ;; + yarn) + # Yarn 1.x maintenance line. TODO: revisit when migrating to v4. + npm_latest_matching yarn '1' + ;; + *) + echo "ERROR: unsupported package manager: $pm" >&2 + exit 1 + ;; + esac +} + +# Run a command inside the plugin directory with the plugin's Node active. +in_plugin() { + ( + cd "$PLUGIN_DIR" + # Activate Node per .nvmrc; install if missing. + if [ -f .nvmrc ]; then + nvm install >/dev/null 2>&1 || true + nvm use >/dev/null 2>&1 + fi + "$@" + ) +} + +# Ensure .npmrc contains save-exact=true so any future add stays exact. +ensure_save_exact_npmrc() { + local f="$PLUGIN_DIR/.npmrc" + if [ ! -f "$f" ]; then + printf 'save-exact=true\n' > "$f" + return + fi + if ! grep -qE '^save-exact[[:space:]]*=' "$f"; then + printf 'save-exact=true\n' >> "$f" + fi +} + +# --- step implementations ----------------------------------------------------- + +step_node_version() { + log "Updating .nvmrc to latest LTS major" + local major + major="$(latest_lts_major)" + echo "$major" > "$PLUGIN_DIR/.nvmrc" + log " .nvmrc -> $major" + # Install + activate so subsequent steps use it. + (cd "$PLUGIN_DIR" && nvm install) +} + +step_pkg_manager() { + require_cmd jq + local pm pm_version + pm="$(detect_pm)" + pm_version="$(latest_pm_version "$pm")" + if [ -z "$pm_version" ]; then + echo "ERROR: failed to determine latest version for '$pm'" >&2 + exit 1 + fi + log "Updating packageManager to ${pm}@${pm_version}" + + # Activate via corepack so the binary is available without polluting global npm. + in_plugin bash -c " + corepack enable >/dev/null 2>&1 || true + corepack prepare '${pm}@${pm_version}' --activate + " + + # Rewrite packageManager field (drop any sha checksum suffix). + local pkg="$PLUGIN_DIR/package.json" + local tmp + tmp="$(mktemp)" + jq --arg v "${pm}@${pm_version}" '.packageManager = $v' "$pkg" > "$tmp" + mv "$tmp" "$pkg" +} + +step_cp_update() { + log "Running @grafana/create-plugin update --force" + in_plugin bash -c "npx --yes @grafana/create-plugin@latest update --force" +} + +# Rewrite every non-exact version string in package.json (dependencies, +# devDependencies, peerDependencies, optionalDependencies, overrides, +# resolutions, pnpm.overrides) to an exact resolved version. +step_pin_exact() { + require_cmd jq + require_cmd node + local pkg="$PLUGIN_DIR/package.json" + log "Pinning all package.json versions to exact" + + ensure_save_exact_npmrc + + # Build a list of "section\tpath\tname\trange" entries to process. + # Sections handled: dependencies, devDependencies, peerDependencies, + # optionalDependencies, overrides (npm/yarn shorthand), resolutions (yarn), + # pnpm.overrides (pnpm). + local entries + entries="$( + jq -r ' + def emit(section; obj): + (obj // {}) | to_entries[] | "\(section)\t\(.key)\t\(.value)"; + emit("dependencies"; .dependencies), + emit("devDependencies"; .devDependencies), + emit("peerDependencies"; .peerDependencies), + emit("optionalDependencies"; .optionalDependencies), + emit("overrides"; .overrides), + emit("resolutions"; .resolutions), + emit("pnpm.overrides"; (.pnpm // {}).overrides) + ' "$pkg" + )" + + if [ -z "$entries" ]; then + return + fi + + local need_install=0 + while IFS=$'\t' read -r section name range; do + [ -z "$section" ] && continue + # Skip nested object overrides (npm allows nested objects); we only + # touch leaf string values. jq emits objects as JSON strings starting + # with "{". + case "$range" in + "{"*) continue ;; + esac + # Skip non-registry sources we cannot resolve safely. + case "$range" in + file:*|link:*|portal:*|workspace:*|git+*|git:*|http:*|https:*|github:*|npm:*) + continue + ;; + esac + # Already exact? Match X, X.Y, X.Y.Z and prerelease/build metadata. + if printf '%s' "$range" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.+-]+)?$'; then + continue + fi + + local resolved + resolved="$(npm view "${name}@${range}" version 2>/dev/null | tail -1 | awk '{print $2}' | tr -d "'\"")" + if [ -z "$resolved" ]; then + # Fall back to the default dist-tag. + resolved="$(npm_latest "$name")" + fi + if [ -z "$resolved" ]; then + log " WARN: could not resolve ${name}@${range}; leaving as-is" + continue + fi + + # Sanity check: resolved must be exact semver. + if ! printf '%s' "$resolved" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.+-]+)?$'; then + log " WARN: resolver returned non-exact value for ${name}: '$resolved'; skipping" + continue + fi + + log " pin: ${section} ${name} ${range} -> ${resolved}" + + local tmp + tmp="$(mktemp)" + case "$section" in + pnpm.overrides) + jq --arg n "$name" --arg v "$resolved" '.pnpm.overrides[$n] = $v' "$pkg" > "$tmp" + ;; + *) + jq --arg s "$section" --arg n "$name" --arg v "$resolved" '.[$s][$n] = $v' "$pkg" > "$tmp" + ;; + esac + mv "$tmp" "$pkg" + need_install=1 + done <<< "$entries" + + # Final sanity: assert no non-exact string values remain in scope. + local bad + bad="$( + jq -r ' + def check(section; obj): + (obj // {}) | to_entries[] + | select((.value | type) == "string") + | select(.value | test("^[0-9]+\\.[0-9]+\\.[0-9]+([-+][0-9A-Za-z.+-]+)?$") | not) + | select(.value | test("^(file:|link:|portal:|workspace:|git\\+|git:|http:|https:|github:|npm:)") | not) + | "\(section): \(.key) = \(.value)"; + check("dependencies"; .dependencies), + check("devDependencies"; .devDependencies), + check("peerDependencies"; .peerDependencies), + check("optionalDependencies"; .optionalDependencies), + check("overrides"; .overrides), + check("resolutions"; .resolutions), + check("pnpm.overrides"; (.pnpm // {}).overrides) + ' "$pkg" + )" + if [ -n "$bad" ]; then + log " WARN: the following entries remain non-exact and could not be resolved:" + printf '%s\n' "$bad" | sed 's/^/ /' >&2 + fi + + if [ "$need_install" -eq 1 ]; then + log "Refreshing lockfile after pinning" + local pm + pm="$(detect_pm)" + in_plugin "$pm" install + fi +} + +step_list_grafana() { + require_cmd jq + # Emit one "
\t\t" per @grafana/* dep for Claude + # to bump. Sections covered: dependencies, devDependencies. + jq -r ' + def emit(section; obj): + (obj // {}) | to_entries[] + | select(.key | startswith("@grafana/")) + | "\(section)\t\(.key)\t\(.value)"; + emit("dependencies"; .dependencies), + emit("devDependencies"; .devDependencies) + ' "$PLUGIN_DIR/package.json" +} + +step_go_version() { + if [ ! -f "$PLUGIN_DIR/go.mod" ]; then + log "No go.mod; skipping go-version" + return + fi + require_cmd curl + require_cmd jq + local v + v="$(latest_go_version)" + log "Setting go directive to $v" + # Replace the top-level `go X.Y[.Z]` directive. + local tmp + tmp="$(mktemp)" + awk -v ver="$v" ' + !done && /^go [0-9]/ { print "go " ver; done=1; next } + { print } + ' "$PLUGIN_DIR/go.mod" > "$tmp" + mv "$tmp" "$PLUGIN_DIR/go.mod" +} + +step_go_sdk() { + if [ ! -f "$PLUGIN_DIR/go.mod" ]; then + log "No go.mod; skipping go-sdk" + return + fi + require_cmd go + log "Bumping grafana-plugin-sdk-go to latest" + (cd "$PLUGIN_DIR" && go get github.com/grafana/grafana-plugin-sdk-go@latest) +} + +step_osv_scan() { + require_cmd osv-scanner + log "Running osv-scanner" + # Try v2 invocation first, fall back to v1. + if osv-scanner --help 2>&1 | grep -q '^ scan'; then + (cd "$PLUGIN_DIR" && osv-scanner scan source --format json -r . || true) + else + (cd "$PLUGIN_DIR" && osv-scanner --format json -r . || true) + fi +} + +step_go_tidy() { + if [ ! -f "$PLUGIN_DIR/go.mod" ]; then + log "No go.mod; skipping go-tidy" + return + fi + require_cmd go + log "go mod tidy" + (cd "$PLUGIN_DIR" && go mod tidy) +} + +step_verify() { + require_cmd jq + local pm + pm="$(detect_pm)" + log "Verifying build with $pm" + in_plugin "$pm" install + in_plugin "$pm" run typecheck + in_plugin "$pm" run lint + in_plugin "$pm" run build + if [ -f "$PLUGIN_DIR/Magefile.go" ]; then + require_cmd mage + log "Building backend via mage" + (cd "$PLUGIN_DIR" && go mod download -x && mage -v buildAll) + fi +} + +# --- dispatch ----------------------------------------------------------------- + +run_step() { + case "$1" in + node-version) step_node_version ;; + pkg-manager) step_pkg_manager ;; + cp-update) step_cp_update ;; + pin-exact) step_pin_exact ;; + list-grafana) step_list_grafana ;; + go-version) step_go_version ;; + go-sdk) step_go_sdk ;; + osv-scan) step_osv_scan ;; + go-tidy) step_go_tidy ;; + verify) step_verify ;; + *) + echo "ERROR: unknown step: $1" >&2 + exit 1 + ;; + esac +} + +if [ "$RUN_ALL" -eq 1 ]; then + # Order matches the slash command's orchestration. list-grafana and + # osv-scan are informational steps Claude consumes; --all still runs them + # so the output is captured for inspection but the real remediation has + # to be driven by Claude. + for s in \ + node-version \ + pkg-manager \ + cp-update \ + pin-exact \ + list-grafana \ + go-version \ + go-sdk \ + osv-scan \ + go-tidy \ + pin-exact \ + verify + do + run_step "$s" + done +else + run_step "$STEP" +fi