Skip to content

feat(figma): make the direct Figma OAuth path work end to end #54

feat(figma): make the direct Figma OAuth path work end to end

feat(figma): make the direct Figma OAuth path work end to end #54

Workflow file for this run

name: CI
# One workflow, matching devup-ui and the other org projects: verification,
# changepacks version management, binary builds and release publication all
# live here rather than in a second file that can drift out of step.
#
# Release flow (driven by changepacks/action, not by hand):
# 1. A pull request touching crates/ must carry a changepack. `changepacks`
# comments the detected packs; `changepack-required` makes it a gate.
# 2. On push to main with pending changepacks, the action opens an
# "Update Versions" pull request that runs `changepacks update`.
# 3. Merging that PR leaves no changepacks, so the action cuts tags and
# *draft* releases and reports them in `pending_releases`.
# 4. `build` compiles every MCP binary for all three platforms and uploads
# them onto those drafts.
# 5. `finalize` publishes the drafts, but only once the uploads succeeded —
# so a release is never visible without its binaries attached.
on:
push:
branches: [main]
pull_request:
permissions:
contents: write
pull-requests: write
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
jobs:
# The action comments the changepack status on a pull request but does not
# fail it. A crate change that ships without a changepack never moves the
# version, so it never releases — this turns that silent outcome into a
# red check with the command to fix it.
changepack-required:
name: changepack required
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Require a changepack for crate changes
shell: bash
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
base="$(git merge-base "$BASE_SHA" "$HEAD_SHA")"
changed="$(git diff --name-only "$base" "$HEAD_SHA")"
crate_changes="$(printf '%s\n' "$changed" | grep -E '^crates/' || true)"
if [ -z "$crate_changes" ]; then
echo "No crate sources touched; a changepack is not required."
exit 0
fi
log_changes="$(printf '%s\n' "$changed" \
| grep -E '^\.changepacks/changepack_log_.*\.json$' || true)"
if [ -n "$log_changes" ]; then
echo "Changepack present:"
printf ' %s\n' $log_changes
exit 0
fi
{
echo "This pull request changes crate sources but adds no changepack log."
echo
echo "Without one the workspace version never moves, so the change"
echo "ships to main and is never released."
echo
echo " cargo install changepacks"
echo " changepacks"
echo
echo "Pick the affected crates, choose Major/Minor/Patch, and write"
echo "the release note, then commit the generated"
echo ".changepacks/changepack_log_*.json alongside your change."
echo
echo "--- crate files changed without a changepack ---"
printf ' %s\n' $crate_changes
} >&2
exit 1
verify:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: 24
- uses: dtolnay/rust-toolchain@1.98.0
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- run: cargo install cargo-insta --version 1.48.0 --locked
- run: cargo fmt --all -- --check
- run: node --test crates/devup-mcp-figma/tests/explore_script_behavior.mjs
- run: cargo test -p devup-mcp --test stdio_smoke
- run: cargo clippy --workspace --all-targets --all-features -- -D warnings
- run: cargo insta test --workspace --all-features --check
- run: cargo build --workspace --release
changepacks:
name: changepacks
needs: verify
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
# changepacks diffs HEAD against the previous release commit; the
# default shallow fetch grafts away every parent, so that lookup
# fails and the release never publishes.
fetch-depth: 0
fetch-tags: true
- uses: changepacks/action@main
id: changepacks
with:
token: ${{ secrets.GITHUB_TOKEN }}
create_release: true
outputs:
changepacks: ${{ steps.changepacks.outputs.changepacks }}
release_assets_urls: ${{ steps.changepacks.outputs.release_assets_urls }}
pending_releases: ${{ steps.changepacks.outputs.pending_releases }}
build:
name: build (${{ matrix.os }})
needs: changepacks
# Only when a draft release is actually waiting for assets. On a pull
# request, or on a push that merely opened the Update Versions PR, there
# is nothing to attach to.
if: >-
needs.changepacks.outputs.pending_releases != ''
&& needs.changepacks.outputs.pending_releases != '{}'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
targets: x86_64-unknown-linux-gnu
suffix: linux-x86_64
ext: ""
- os: windows-latest
targets: x86_64-pc-windows-msvc
suffix: windows-x86_64
ext: ".exe"
- os: macos-latest
# Fused into one universal binary so a single macOS asset runs on
# both Apple Silicon and Intel.
targets: aarch64-apple-darwin x86_64-apple-darwin
suffix: macos-universal
ext: ""
steps:
- uses: actions/checkout@v7
- uses: dtolnay/rust-toolchain@1.98.0
- uses: Swatinem/rust-cache@v2
- name: Build release binaries
shell: bash
env:
TARGETS: ${{ matrix.targets }}
SUFFIX: ${{ matrix.suffix }}
EXT: ${{ matrix.ext }}
OS: ${{ matrix.os }}
run: |
set -euo pipefail
for target in $TARGETS; do
rustup target add "$target"
cargo build --release --target "$target" -p devup-mcp -p devup-mcp-visual
done
mkdir -p dist
for bin in devup-mcp devup-mcp-visual; do
out="dist/${bin}-${SUFFIX}${EXT}"
if [ "$OS" = "macos-latest" ]; then
lipo -create -output "$out" \
"target/aarch64-apple-darwin/release/${bin}" \
"target/x86_64-apple-darwin/release/${bin}"
file "$out"
else
set -- $TARGETS
cp "target/$1/release/${bin}${EXT}" "$out"
fi
done
ls -l dist
- name: Upload binaries onto the draft release
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ASSET_URLS: ${{ needs.changepacks.outputs.release_assets_urls }}
run: |
set -euo pipefail
# release_assets_urls maps project path -> asset upload URL. The
# binaries belong to the devup-mcp crate; the library crates get
# their own releases with no assets.
upload="$(printf '%s' "$ASSET_URLS" \
| jq -r '.["crates/devup-mcp/Cargo.toml"] // empty')"
if [ -z "$upload" ]; then
echo "no asset upload URL for crates/devup-mcp/Cargo.toml" >&2
printf '%s\n' "$ASSET_URLS" >&2
exit 1
fi
# Drop the RFC 6570 template suffix, e.g. "{?name,label}".
upload="${upload%%\{*}"
for file in dist/*; do
name="$(basename "$file")"
echo "uploading $name"
curl --fail-with-body -sS -X POST \
-H "Authorization: Bearer $GH_TOKEN" \
-H "Content-Type: application/octet-stream" \
--data-binary @"$file" \
"${upload}?name=${name}" >/dev/null
done
finalize:
name: finalize release
needs: [changepacks, build]
if: >-
needs.changepacks.outputs.pending_releases != ''
&& needs.changepacks.outputs.pending_releases != '{}'
runs-on: ubuntu-latest
steps:
# Finalize-only: the action neither installs changepacks nor touches the
# repository here, so no checkout is needed. Running it after `build`
# is what guarantees a published release always has its binaries.
- uses: changepacks/action@main
with:
token: ${{ secrets.GITHUB_TOKEN }}
finalize_releases: ${{ needs.changepacks.outputs.pending_releases }}