mirror of
https://github.com/glittercowboy/get-shit-done
synced 2026-04-25 17:25:23 +02:00
* ci: explicit rebase check + fail-fast SDK typecheck in install-smoke Stale-base regression guard. Root cause: GitHub's `refs/pull/N/merge` is cached against the PR's recorded merge-base, not current main. When main advances after a PR is opened, the cache stays stale and CI runs against the pre-advance tree. PRs hit this whenever a type error lands on main and gets patched shortly after (e.g. #2611 + #2622) — stale branches replay the broken intermediate state and report confusing downstream failures for hours. Observed failure mode: install-smoke's "Assert gsd-sdk resolves on PATH" step fires with "installSdkIfNeeded() regression" even when the real cause is `npm run build` failing in sdk/ due to a TypeScript cast mismatch already fixed on main. Fix: - Explicit `git merge origin/main` step in both `install-smoke.yml` and `test.yml`. If the merge conflicts, emit a clear "rebase onto main" diagnostic and fail early, rather than let conflicts produce unrelated downstream errors. - Dedicated `npm run build:sdk` typecheck step in install-smoke with a remediation hint ("rebase onto main — the error may already be fixed on trunk"). Fails fast with the actual tsc output instead of masking it behind a PATH assertion. - Drop the `|| true` on `get-shit-done-cc --claude --local` so installer failures surface at the install step with install.js's own error message, not at the downstream PATH assertion where the message misleadingly blames "shim regression". - `fetch-depth: 0` on checkout so the merge-base check has history. * ci: address CodeRabbit — add rebase check to smoke-unpacked, fix fetch flag Two findings from CodeRabbit's review on #2631: 1. `smoke-unpacked` job was missing the same rebase check applied to the `smoke` job. It ran on the cached `refs/pull/N/merge` and could hit the same stale-base failure mode the PR was designed to prevent. Added the identical rebase-check step. 2. `git fetch origin main --depth=0` is an invalid flag — git rejects it with "depth 0 is not a positive number". The intent was "fetch with full depth", but the right way is just `git fetch origin main` (no --depth). Removed the invalid flag and the `||` fallback that was papering over the error.
299 lines
12 KiB
YAML
299 lines
12 KiB
YAML
name: Install Smoke
|
|
|
|
# Exercises the real install paths:
|
|
# tarball: `npm pack` → `npm install -g <tarball>` → assert gsd-sdk on PATH
|
|
# unpacked: `npm install -g <dir>` (no pack) → assert gsd-sdk on PATH + executable
|
|
#
|
|
# The tarball path is the canonical ship path. The unpacked path reproduces the
|
|
# mode-644 failure class (issue #2453): npm does NOT chmod bin targets when
|
|
# installing from an unpacked local directory, so any stale tsc output lacking
|
|
# execute bits will be caught by the unpacked job before release.
|
|
#
|
|
# - PRs: path-filtered, minimal runner (ubuntu + Node LTS) for fast signal.
|
|
# - Push to release branches / main: full matrix.
|
|
# - workflow_call: invoked from release.yml as a pre-publish gate.
|
|
|
|
on:
|
|
pull_request:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- 'bin/install.js'
|
|
- 'bin/gsd-sdk.js'
|
|
- 'sdk/**'
|
|
- 'package.json'
|
|
- 'package-lock.json'
|
|
- '.github/workflows/install-smoke.yml'
|
|
- '.github/workflows/release.yml'
|
|
push:
|
|
branches:
|
|
- main
|
|
- 'release/**'
|
|
- 'hotfix/**'
|
|
workflow_call:
|
|
inputs:
|
|
ref:
|
|
description: 'Git ref to check out (branch or SHA). Defaults to the triggering ref.'
|
|
required: false
|
|
type: string
|
|
default: ''
|
|
workflow_dispatch:
|
|
|
|
concurrency:
|
|
group: install-smoke-${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
cancel-in-progress: true
|
|
|
|
jobs:
|
|
# ---------------------------------------------------------------------------
|
|
# Job 1: tarball install (existing canonical path)
|
|
# ---------------------------------------------------------------------------
|
|
smoke:
|
|
runs-on: ${{ matrix.os }}
|
|
timeout-minutes: 12
|
|
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
# PRs run the minimal path (ubuntu + LTS). Pushes / release branches
|
|
# and workflow_call add macOS + Node 24 coverage.
|
|
include:
|
|
- os: ubuntu-latest
|
|
node-version: 22
|
|
full_only: false
|
|
- os: ubuntu-latest
|
|
node-version: 24
|
|
full_only: true
|
|
- os: macos-latest
|
|
node-version: 24
|
|
full_only: true
|
|
|
|
steps:
|
|
- name: Skip full-only matrix entry on PR
|
|
id: skip
|
|
shell: bash
|
|
env:
|
|
EVENT: ${{ github.event_name }}
|
|
FULL_ONLY: ${{ matrix.full_only }}
|
|
run: |
|
|
if [ "$EVENT" = "pull_request" ] && [ "$FULL_ONLY" = "true" ]; then
|
|
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
else
|
|
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
if: steps.skip.outputs.skip != 'true'
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
# Need enough history to merge origin/main for stale-base detection.
|
|
fetch-depth: 0
|
|
|
|
# The default `refs/pull/N/merge` ref GitHub produces for PRs is cached
|
|
# against the recorded merge-base, not current main. When main advances
|
|
# after the PR was opened, the merge ref stays stale and CI can fail on
|
|
# issues that were already fixed upstream. Explicitly merge current
|
|
# origin/main into the PR head so smoke always tests the PR against the
|
|
# latest trunk. If the merge conflicts, emit a clear "rebase onto main"
|
|
# diagnostic instead of a downstream build error that looks unrelated.
|
|
- name: Rebase check — merge origin/main into PR head
|
|
if: steps.skip.outputs.skip != 'true' && github.event_name == 'pull_request'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
git config user.email "ci@gsd-build"
|
|
git config user.name "CI Rebase Check"
|
|
git fetch origin main
|
|
if ! git merge --no-edit --no-ff origin/main; then
|
|
echo "::error::This PR cannot cleanly merge origin/main. Rebase your branch onto current main and push again."
|
|
echo "::error::Conflicting files:"
|
|
git diff --name-only --diff-filter=U
|
|
git merge --abort
|
|
exit 1
|
|
fi
|
|
|
|
- name: Set up Node.js ${{ matrix.node-version }}
|
|
if: steps.skip.outputs.skip != 'true'
|
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
|
with:
|
|
node-version: ${{ matrix.node-version }}
|
|
cache: 'npm'
|
|
|
|
- name: Install root deps
|
|
if: steps.skip.outputs.skip != 'true'
|
|
run: npm ci
|
|
|
|
# Isolated SDK typecheck — if the build fails, emit a clear "stale base
|
|
# or real type error" diagnostic instead of letting the failure cascade
|
|
# into the tarball install step, where the downstream PATH assertion
|
|
# misreports it as "gsd-sdk not on PATH — installSdkIfNeeded regression".
|
|
- name: SDK typecheck (fails fast on type regressions)
|
|
if: steps.skip.outputs.skip != 'true'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if ! npm run build:sdk; then
|
|
echo "::error::SDK build (npm run build:sdk) failed."
|
|
echo "::error::Common cause: your PR base is behind main and picks up intermediate type errors that are already fixed on trunk."
|
|
echo "::error::Fix: git fetch origin main && git rebase origin/main && git push --force-with-lease"
|
|
echo "::error::If the error persists on a fresh rebase, the type error is real — fix it in sdk/src/ and push."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Pack root tarball
|
|
if: steps.skip.outputs.skip != 'true'
|
|
id: pack
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
npm pack --silent
|
|
TARBALL=$(ls get-shit-done-cc-*.tgz | head -1)
|
|
echo "tarball=$TARBALL" >> "$GITHUB_OUTPUT"
|
|
echo "Packed: $TARBALL"
|
|
|
|
- name: Ensure npm global bin is on PATH (CI runner default may differ)
|
|
if: steps.skip.outputs.skip != 'true'
|
|
shell: bash
|
|
run: |
|
|
NPM_BIN="$(npm config get prefix)/bin"
|
|
echo "$NPM_BIN" >> "$GITHUB_PATH"
|
|
echo "npm global bin: $NPM_BIN"
|
|
|
|
- name: Install tarball globally
|
|
if: steps.skip.outputs.skip != 'true'
|
|
shell: bash
|
|
env:
|
|
TARBALL: ${{ steps.pack.outputs.tarball }}
|
|
WORKSPACE: ${{ github.workspace }}
|
|
run: |
|
|
set -euo pipefail
|
|
TMPDIR_ROOT=$(mktemp -d)
|
|
cd "$TMPDIR_ROOT"
|
|
npm install -g "$WORKSPACE/$TARBALL"
|
|
command -v get-shit-done-cc
|
|
# `--claude --local` is the non-interactive code path. Don't swallow
|
|
# non-zero exit — if the installer fails, that IS the CI failure, and
|
|
# its own error message is more useful than the downstream "shim
|
|
# regression" assertion masking the real cause.
|
|
if ! get-shit-done-cc --claude --local; then
|
|
echo "::error::get-shit-done-cc --claude --local failed. See the install.js output above for the real error (SDK build, PATH resolution, chmod, etc.)."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Assert gsd-sdk resolves on PATH
|
|
if: steps.skip.outputs.skip != 'true'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if ! command -v gsd-sdk >/dev/null 2>&1; then
|
|
echo "::error::gsd-sdk is not on PATH after tarball install — shim regression"
|
|
NPM_BIN="$(npm config get prefix)/bin"
|
|
echo "npm global bin: $NPM_BIN"
|
|
ls -la "$NPM_BIN" | grep -i gsd || true
|
|
exit 1
|
|
fi
|
|
echo "✓ gsd-sdk resolves at: $(command -v gsd-sdk)"
|
|
|
|
- name: Assert gsd-sdk is executable
|
|
if: steps.skip.outputs.skip != 'true'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
gsd-sdk --version || gsd-sdk --help
|
|
echo "✓ gsd-sdk is executable"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Job 2: unpacked-dir install — reproduces the mode-644 failure class (#2453)
|
|
#
|
|
# `npm install -g <directory>` does NOT chmod bin targets when the source
|
|
# file was produced by a build script (tsc emits 0o644). This job catches
|
|
# regressions where sdk/dist/cli.js loses its execute bit before publish.
|
|
# ---------------------------------------------------------------------------
|
|
smoke-unpacked:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
|
|
steps:
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
ref: ${{ inputs.ref || github.ref }}
|
|
fetch-depth: 0
|
|
|
|
# See the `smoke` job above for rationale — refs/pull/N/merge is cached
|
|
# against the recorded merge-base, not current main. Explicitly merge
|
|
# origin/main so smoke-unpacked also runs against the latest trunk.
|
|
- name: Rebase check — merge origin/main into PR head
|
|
if: github.event_name == 'pull_request'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
git config user.email "ci@gsd-build"
|
|
git config user.name "CI Rebase Check"
|
|
git fetch origin main
|
|
if ! git merge --no-edit --no-ff origin/main; then
|
|
echo "::error::This PR cannot cleanly merge origin/main. Rebase your branch onto current main and push again."
|
|
echo "::error::Conflicting files:"
|
|
git diff --name-only --diff-filter=U
|
|
git merge --abort
|
|
exit 1
|
|
fi
|
|
|
|
- name: Set up Node.js 22
|
|
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
|
with:
|
|
node-version: 22
|
|
cache: 'npm'
|
|
|
|
- name: Install root deps
|
|
run: npm ci
|
|
|
|
- name: Build SDK dist (sdk/dist is gitignored — must build for unpacked install)
|
|
run: npm run build:sdk
|
|
|
|
- name: Ensure npm global bin is on PATH
|
|
shell: bash
|
|
run: |
|
|
NPM_BIN="$(npm config get prefix)/bin"
|
|
echo "$NPM_BIN" >> "$GITHUB_PATH"
|
|
echo "npm global bin: $NPM_BIN"
|
|
|
|
- name: Strip execute bit from sdk/dist/cli.js to simulate tsc-fresh output
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
# Simulate the exact state tsc produces: cli.js at mode 644.
|
|
chmod 644 sdk/dist/cli.js
|
|
echo "Stripped execute bit: $(stat -c '%a' sdk/dist/cli.js 2>/dev/null || stat -f '%p' sdk/dist/cli.js)"
|
|
|
|
- name: Install from unpacked directory (no npm pack)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
TMPDIR_ROOT=$(mktemp -d)
|
|
cd "$TMPDIR_ROOT"
|
|
npm install -g "$GITHUB_WORKSPACE"
|
|
command -v get-shit-done-cc
|
|
get-shit-done-cc --claude --local || true
|
|
|
|
- name: Assert gsd-sdk resolves on PATH after unpacked install
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
if ! command -v gsd-sdk >/dev/null 2>&1; then
|
|
echo "::error::gsd-sdk is not on PATH after unpacked install — #2453 regression"
|
|
NPM_BIN="$(npm config get prefix)/bin"
|
|
ls -la "$NPM_BIN" | grep -i gsd || true
|
|
exit 1
|
|
fi
|
|
echo "✓ gsd-sdk resolves at: $(command -v gsd-sdk)"
|
|
|
|
- name: Assert gsd-sdk is executable after unpacked install (#2453)
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
# This is the exact check that would have caught #2453 before release.
|
|
# The shim (bin/gsd-sdk.js) invokes sdk/dist/cli.js via `node`, so
|
|
# the execute bit on cli.js is not needed for the shim path. However
|
|
# installSdkIfNeeded() also chmods cli.js in-place as a safety net.
|
|
gsd-sdk --version || gsd-sdk --help
|
|
echo "✓ gsd-sdk is executable after unpacked install"
|