Skip to content

Install smoke test

Install smoke test #7

Workflow file for this run

name: Install smoke test
# Verifies that each install channel produces a working `plumb` binary
# across platforms. Runs weekly and on every release tag.
#
# Matrix: {cargo, brew, npm, curl} x {ubuntu, macos, windows}.
# All four install channels (cargo, brew, npm, curl) run live against
# the latest published tag.
# See dist-workspace.toml for channel wiring details.
#
# Non-gated failures open or update a single tracking issue so the team
# gets notified without duplicate noise.
on:
schedule:
- cron: "15 4 * * 1" # Weekly, Monday 04:15 UTC
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+"
- "v[0-9]+.[0-9]+.[0-9]+-*"
workflow_dispatch:
inputs:
tag:
description: "Release tag to smoke (e.g. v0.0.11). Leave empty to use the latest release."
required: false
type: string
default: ""
permissions:
contents: read
concurrency:
group: install-smoke-${{ github.ref }}
cancel-in-progress: true
jobs:
resolve-tag:
name: Resolve release tag
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
version: ${{ steps.tag.outputs.version }}
steps:
- name: Determine tag
id: tag
env:
GH_TOKEN: ${{ github.token }}
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
REPO: ${{ github.repository }}
INPUT_TAG: ${{ inputs.tag }}
run: |
if [[ -n "$INPUT_TAG" ]]; then
tag="$INPUT_TAG"
elif [[ "$REF_TYPE" == "tag" ]]; then
tag="$REF_NAME"
else
tag="$(gh release view --repo "$REPO" --json tagName -q .tagName 2>/dev/null || true)"
fi
if [[ -z "$tag" ]]; then
echo "::warning::No release tag found — skipping smoke tests."
echo "tag=" >> "$GITHUB_OUTPUT"
echo "version=" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "version=${tag#v}" >> "$GITHUB_OUTPUT"
echo "Resolved tag: $tag"
smoke:
name: "${{ matrix.channel }} / ${{ matrix.os }}"
needs: resolve-tag
if: needs.resolve-tag.outputs.tag != ''
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.gated }}
strategy:
fail-fast: false
matrix:
include:
# ── cargo ───────────────────────────────────────────────
- channel: cargo
os: ubuntu-latest
gated: false
- channel: cargo
os: macos-latest
gated: false
- channel: cargo
os: windows-latest
gated: false
# ── curl (install script) ───────────────────────────────
- channel: curl
os: ubuntu-latest
gated: false
- channel: curl
os: macos-latest
gated: false
- channel: curl
os: windows-latest
gated: false
# ── brew (live tap aram-devdocs/homebrew-plumb) ─────────
- channel: brew
os: ubuntu-latest
gated: false
- channel: brew
os: macos-latest
gated: false
# ── npm (unscoped `plumb-cli`) ──────────────────────────
- channel: npm
os: ubuntu-latest
gated: false
- channel: npm
os: macos-latest
gated: false
- channel: npm
os: windows-latest
gated: false
steps:
- name: Skip gated channel
if: matrix.gated
env:
CHANNEL: ${{ matrix.channel }}
run: |
echo "::notice::$CHANNEL is gated — external prerequisites not yet available. Skipping."
exit 0
# ── cargo install ─────────────────────────────────────────
- name: Install via cargo
if: "!matrix.gated && matrix.channel == 'cargo'"
timeout-minutes: 45
shell: bash
env:
VERSION: ${{ needs.resolve-tag.outputs.version }}
run: |
cargo install plumb-cli --version "$VERSION" --locked
# ── curl / install script ─────────────────────────────────
- name: Install via curl (unix)
if: "!matrix.gated && matrix.channel == 'curl' && runner.os != 'Windows'"
shell: bash
env:
INSTALLER_URL: "https://github.com/${{ github.repository }}/releases/download/${{ needs.resolve-tag.outputs.tag }}/plumb-cli-installer.sh"
REPO: ${{ github.repository }}
run: |
curl -LsSf -o plumb-cli-installer.sh "$INSTALLER_URL"
gh attestation verify plumb-cli-installer.sh --repo "$REPO"
sh plumb-cli-installer.sh
- name: Install via curl (windows)
if: "!matrix.gated && matrix.channel == 'curl' && runner.os == 'Windows'"
shell: pwsh
env:
INSTALLER_URL: "https://github.com/${{ github.repository }}/releases/download/${{ needs.resolve-tag.outputs.tag }}/plumb-cli-installer.ps1"
REPO: ${{ github.repository }}
run: |
$installer = Join-Path $PWD "plumb-cli-installer.ps1"
Invoke-WebRequest -Uri "$env:INSTALLER_URL" -OutFile $installer
gh attestation verify $installer --repo "$env:REPO"
& $installer
# ── brew ──────────────────────────────────────────────────
- name: Install via brew
if: "!matrix.gated && matrix.channel == 'brew'"
shell: bash
run: |
brew install aram-devdocs/plumb/plumb
# ── npm ───────────────────────────────────────────────────
- name: Install via npm
if: "!matrix.gated && matrix.channel == 'npm'"
shell: bash
run: |
npm install -g plumb-cli
# ── Verify ────────────────────────────────────────────────
- name: Verify plumb
if: "!matrix.gated"
shell: bash
run: |
# On Windows the installer may update PATH outside bash's view.
# Refresh and show diagnostics so PATH failures are explicit.
if [[ "$RUNNER_OS" == "Windows" ]]; then
export PATH="$USERPROFILE/.cargo/bin:$LOCALAPPDATA/Programs/plumb/bin:$PATH"
echo "--- PATH diagnostic ---"
which plumb || { echo "::error::plumb not found on PATH"; where plumb 2>/dev/null || true; echo "PATH=$PATH"; exit 1; }
fi
echo "--- plumb --version ---"
plumb --version
echo "--- plumb lint plumb-fake://hello ---"
rc=0; plumb lint plumb-fake://hello || rc=$?
if [[ "$rc" -ne 0 && "$rc" -ne 3 ]]; then
echo "::error::plumb lint exited with unexpected code $rc"
exit 1
fi
report:
name: Report failures
needs: [resolve-tag, smoke]
if: always() && needs.resolve-tag.outputs.tag != '' && needs.smoke.result == 'failure'
runs-on: ubuntu-latest
permissions:
issues: write
env:
GH_TOKEN: ${{ github.token }}
REPORT_REPO: ${{ github.repository }}
REPORT_TAG: ${{ needs.resolve-tag.outputs.tag }}
REPORT_EVENT_NAME: ${{ github.event_name }}
REPORT_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
steps:
- name: Check out repository
uses: actions/checkout@v6
- name: Assert shell runs use env indirection
shell: bash
run: |
python3 - <<'PY'
from pathlib import Path
import sys
workflow_path = Path(".github/workflows/install-smoke.yml")
lines = workflow_path.read_text().splitlines()
# This is a small stdlib guard for this workflow, not a general
# YAML parser. It intentionally fail-closes on syntax it does not
# recognize instead of trying to accept every GitHub Actions form.
expr_open = "$" + "{{"
# ubuntu-latest currently provides Python 3.10+, so `str | None`
# is valid here without importing Optional.
supported_run_markers = {"|", "|-", "|+"}
in_jobs = False
current_job = None
in_steps = False
current_step = None
in_run = False
run_indent = None
bad_lines = []
unsupported = []
# The scanner assumes the repo-standard 2-space GitHub Actions
# indentation used in this file and block-scalar `run: |`,
# `run: |-`, and `run: |+` forms.
def step_label(step_name: str | None) -> str:
return step_name or "<unnamed step>"
for lineno, line in enumerate(lines, start=1):
stripped = line.lstrip()
indent = len(line) - len(stripped)
if not stripped or stripped.startswith("#"):
continue
if in_run:
if indent > run_indent:
if expr_open in line:
bad_lines.append((lineno, current_job, step_label(current_step), line.strip()))
continue
in_run = False
run_indent = None
if indent == 0 and stripped == "jobs:":
in_jobs = True
current_job = None
in_steps = False
current_step = None
continue
if indent == 0 and in_jobs:
in_jobs = False
current_job = None
in_steps = False
current_step = None
if not in_jobs:
continue
if indent == 2 and stripped.endswith(":"):
current_job = stripped[:-1]
in_steps = False
current_step = None
continue
if indent == 4 and stripped == "steps:":
in_steps = True
current_step = None
continue
if indent <= 4 and stripped != "steps:":
in_steps = False
current_step = None
if not in_steps:
continue
if indent == 6 and stripped.startswith("- "):
current_step = None
if stripped.startswith("- name:"):
current_step = stripped[len("- name:"):].strip()
continue
if stripped.startswith("- uses:"):
current_step = f"uses {stripped[len('- uses:'):].strip()}"
continue
if stripped.startswith("- run:"):
unsupported.append(
(lineno, current_job, step_label(current_step), "inline list-item run syntax is unsupported")
)
continue
if indent == 8 and stripped.startswith("name:"):
current_step = stripped[len("name:"):].strip()
continue
if indent == 8 and stripped.startswith("run:"):
marker = stripped[len("run:"):].strip()
if marker not in supported_run_markers:
unsupported.append(
(
lineno,
current_job,
step_label(current_step),
f"unsupported run syntax: {stripped}",
)
)
continue
in_run = True
run_indent = indent
continue
if in_run:
in_run = False
if unsupported or bad_lines:
for lineno, job_name, step_name, message in unsupported:
print(
f"{workflow_path}:{lineno}: job {job_name!r} step {step_name!r}: {message}",
file=sys.stderr,
)
for lineno, job_name, step_name, content in bad_lines:
print(
f"{workflow_path}:{lineno}: job {job_name!r} step {step_name!r} contains direct workflow interpolation: {content}",
file=sys.stderr,
)
sys.exit(1)
PY
- name: Ensure install-smoke label exists
run: |
gh label create install-smoke \
--repo "$REPORT_REPO" \
--description "Auto-created by the install-smoke workflow" \
--color "D93F0B" 2>/dev/null || true
- name: Create or update tracking issue
run: |
repo="$REPORT_REPO"
title="Install smoke test failure ($REPORT_TAG)"
run_url="$REPORT_RUN_URL"
body="$(cat <<EOF
## Install smoke test failure
**Tag:** \`$REPORT_TAG\`
**Run:** $run_url
**Triggered by:** $REPORT_EVENT_NAME
One or more non-gated install channels failed. Check the workflow
run linked above for per-job logs.
_This issue was created automatically by the install-smoke workflow._
EOF
)"
# Label-based lookup is consistent (no search-index delay).
existing="$(gh issue list --repo "$repo" --state open \
--label install-smoke \
--json number -q '.[0].number' 2>/dev/null || true)"
if [[ -n "$existing" ]]; then
echo "Updating existing issue #$existing"
gh issue comment "$existing" --repo "$repo" \
--body "New failure for tag \`$REPORT_TAG\`: $run_url"
else
echo "Creating new tracking issue"
gh issue create --repo "$repo" \
--title "$title" \
--body "$body" \
--label install-smoke
fi