Skip to content

fix: add -L flag for screen log capture on macOS 4.00.03 (issue #96) #113

fix: add -L flag for screen log capture on macOS 4.00.03 (issue #96)

fix: add -L flag for screen log capture on macOS 4.00.03 (issue #96) #113

Workflow file for this run

name: Rust CI/CD
on:
push:
branches:
- main
paths:
- 'rust/**'
- 'lib/**'
- 'scripts/**'
- '.github/workflows/rust.yml'
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'rust/**'
- 'lib/**'
- 'scripts/**'
- '.github/workflows/rust.yml'
workflow_dispatch:
inputs:
bump_type:
description: 'Version bump type'
required: true
type: choice
options:
- patch
- minor
- major
description:
description: 'Release description (optional)'
required: false
type: string
concurrency:
group: rust-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
CARGO_TERM_COLOR: always
RUSTFLAGS: -Dwarnings
jobs:
# === DETECT CHANGES ===
detect-changes:
name: Detect Changes
runs-on: ubuntu-latest
if: github.event_name != 'workflow_dispatch'
outputs:
rs-changed: ${{ steps.changes.outputs.rs-changed }}
toml-changed: ${{ steps.changes.outputs.toml-changed }}
mjs-changed: ${{ steps.changes.outputs.mjs-changed }}
docs-changed: ${{ steps.changes.outputs.docs-changed }}
workflow-changed: ${{ steps.changes.outputs.workflow-changed }}
any-rust-code-changed: ${{ steps.changes.outputs.any-rust-code-changed }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Detect changes
id: changes
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: node scripts/detect-code-changes.mjs
# === VERSION CHANGE CHECK ===
# Prohibit manual version changes in Cargo.toml - versions should only be changed by CI/CD
version-check:
name: Check for Manual Version Changes
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for version changes in Cargo.toml
run: |
# Get the diff for Cargo.toml
VERSION_DIFF=$(git diff origin/${{ github.base_ref }}...HEAD -- rust/Cargo.toml | grep -E '^\+version\s*=' || true)
if [ -n "$VERSION_DIFF" ]; then
echo "::error::Manual version change detected in rust/Cargo.toml"
echo ""
echo "Version changes in Cargo.toml are prohibited in pull requests."
echo "Versions are managed automatically by the CI/CD pipeline using changelog fragments."
echo ""
echo "To request a release:"
echo " 1. Add a changelog fragment in rust/changelog.d/ describing your changes"
echo " 2. The release workflow will automatically bump the version when merged"
echo ""
echo "Detected change:"
echo "$VERSION_DIFF"
exit 1
fi
echo "No manual version changes detected - check passed"
# === CHANGELOG CHECK ===
changelog:
name: Changelog Fragment Check
runs-on: ubuntu-latest
needs: [detect-changes]
if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-rust-code-changed == 'true'
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check for changelog fragments
run: |
# Get changed files in PR
CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD)
# Check if any Rust source files changed
SOURCE_CHANGED=$(echo "$CHANGED_FILES" | grep -E "^rust/(src/|tests/|Cargo\.toml)" | wc -l)
# Check if a changelog fragment was ADDED in this PR (not just existing)
# We need to check the diff, not just the directory contents
FRAGMENT_ADDED=$(echo "$CHANGED_FILES" | grep -E "^rust/changelog\.d/.*\.md$" | grep -v "README.md" | wc -l)
echo "Source files changed: $SOURCE_CHANGED"
echo "Changelog fragments added in PR: $FRAGMENT_ADDED"
if [ "$SOURCE_CHANGED" -gt 0 ] && [ "$FRAGMENT_ADDED" -eq 0 ]; then
echo "::error::No changelog fragment found in this PR. Please add a changelog entry in rust/changelog.d/"
echo ""
echo "To create a changelog fragment:"
echo " Create a new .md file in rust/changelog.d/ with your changes"
echo " Name it after your PR number, e.g., '123.md'"
echo ""
echo "See rust/changelog.d/README.md for more information."
exit 1
fi
echo "Changelog check passed"
# === LINT AND FORMAT CHECK ===
lint:
name: Lint and Format Check
runs-on: ubuntu-latest
needs: [detect-changes]
if: |
github.event_name == 'push' ||
github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.rs-changed == 'true' ||
needs.detect-changes.outputs.toml-changed == 'true' ||
needs.detect-changes.outputs.mjs-changed == 'true' ||
needs.detect-changes.outputs.docs-changed == 'true' ||
needs.detect-changes.outputs.workflow-changed == 'true'
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Check formatting
working-directory: rust
run: cargo fmt --all -- --check
- name: Run Clippy
working-directory: rust
run: cargo clippy --all-targets --all-features
- name: Check file size limit
run: node scripts/check-file-size.mjs
# === TEST ===
test:
name: Test (${{ matrix.os }})
runs-on: ${{ matrix.os }}
needs: [detect-changes, changelog]
if: always() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped')
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Run tests
working-directory: rust
run: cargo test --all-features --verbose
- name: Run doc tests
working-directory: rust
run: cargo test --doc --verbose
# Build the binary for execution tracking tests
- name: Build release
working-directory: rust
run: cargo build --release
# Test execution tracking (Unix)
- name: Test execution tracking (Unix)
if: runner.os != 'Windows'
working-directory: rust
env:
START_VERBOSE: '1'
run: |
./target/release/start echo "Testing execution tracking"
ls -la ~/.start-command/
cat ~/.start-command/executions.lino
echo "Execution tracking test passed"
# Test execution tracking (Windows)
- name: Test execution tracking (Windows)
if: runner.os == 'Windows'
working-directory: rust
env:
START_VERBOSE: '1'
shell: bash
run: |
./target/release/start.exe echo "Testing execution tracking"
ls -la "$USERPROFILE/.start-command/"
cat "$USERPROFILE/.start-command/executions.lino"
echo "Execution tracking test passed"
# === TEST PARITY CHECK ===
# Fail if Rust has at least 10% fewer test cases than JavaScript
test-parity:
name: Test Count Parity (Rust vs JS)
runs-on: ubuntu-latest
needs: [detect-changes]
if: always() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.any-rust-code-changed == 'true')
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Check Rust/JS test count parity
run: node scripts/check-test-parity.mjs
# === CODE COVERAGE ===
# Fail if Rust test coverage drops below 80%
coverage:
name: Code Coverage (Rust, Linux only)
runs-on: ubuntu-latest
needs: [detect-changes, changelog]
if: always() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped')
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-coverage-
- name: Install cargo-tarpaulin
run: cargo install cargo-tarpaulin
- name: Run coverage
working-directory: rust
run: |
# tarpaulin.toml excludes src/bin/main.rs (CLI entry point, not library logic)
mkdir -p coverage
cargo tarpaulin --all-features --timeout 120 --out Xml --output-dir coverage/ 2>&1 | tee coverage/tarpaulin.log
# tarpaulin outputs: "50.73% coverage, 908/1790 lines covered"
COVERAGE=$(grep -oP '^\d+\.\d+(?=% coverage)' coverage/tarpaulin.log | tail -1)
echo "Coverage: ${COVERAGE}%"
echo "coverage=${COVERAGE}" >> $GITHUB_OUTPUT
# Fail if coverage is below 50% (lib code only; main.rs excluded via tarpaulin.toml)
if python3 -c "import sys; c=float('${COVERAGE}') if '${COVERAGE}' else 0; sys.exit(0 if c >= 50.0 else 1)"; then
echo "✅ Coverage ${COVERAGE}% meets the 50% threshold"
else
echo "❌ Coverage ${COVERAGE}% is below the 50% threshold"
exit 1
fi
# === BUILD ===
# Only build on push to main, not on PRs (tests already verify the code builds)
build:
name: Build Package
runs-on: ubuntu-latest
needs: [lint, test]
if: always() && github.event_name == 'push' && needs.lint.result == 'success' && needs.test.result == 'success'
steps:
- uses: actions/checkout@v4
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Cache cargo registry
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: ${{ runner.os }}-cargo-build-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-build-
- name: Build release
working-directory: rust
run: cargo build --release --verbose
- name: Check package
working-directory: rust
run: cargo package --list
# === AUTO RELEASE ===
auto-release:
name: Auto Release
needs: [lint, test, build]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Determine bump type from changelog fragments
id: bump_type
run: node scripts/get-bump-type.mjs
- name: Check if version already released or no fragments
id: check
working-directory: rust
run: |
if [ "${{ steps.bump_type.outputs.has_fragments }}" != "true" ]; then
CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml)
if git rev-parse "rust-v$CURRENT_VERSION" >/dev/null 2>&1; then
echo "No changelog fragments and rust-v$CURRENT_VERSION already released"
echo "should_release=false" >> $GITHUB_OUTPUT
else
echo "No changelog fragments but rust-v$CURRENT_VERSION not yet released"
echo "should_release=true" >> $GITHUB_OUTPUT
echo "skip_bump=true" >> $GITHUB_OUTPUT
fi
else
echo "Found changelog fragments, proceeding with release"
echo "should_release=true" >> $GITHUB_OUTPUT
echo "skip_bump=false" >> $GITHUB_OUTPUT
fi
- name: Collect changelog and bump version
id: version
if: steps.check.outputs.should_release == 'true' && steps.check.outputs.skip_bump != 'true'
run: |
node scripts/version-and-commit.mjs \
--bump-type "${{ steps.bump_type.outputs.bump_type }}" \
--working-dir rust \
--mode changelog
- name: Get current version
id: current_version
if: steps.check.outputs.should_release == 'true'
working-directory: rust
run: |
CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml)
echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
- name: Build release
if: steps.check.outputs.should_release == 'true'
working-directory: rust
run: cargo build --release
- name: Create GitHub Release
if: steps.check.outputs.should_release == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/create-github-release.mjs \
--release-version "${{ steps.current_version.outputs.version }}" \
--repository "${{ github.repository }}" \
--prefix "rust-"
# === MANUAL RELEASE ===
manual-release:
name: Manual Release
needs: [lint, test, build]
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.x'
- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
- name: Collect changelog fragments
run: |
FRAGMENTS=$(find rust/changelog.d -name "*.md" ! -name "README.md" 2>/dev/null | wc -l)
if [ "$FRAGMENTS" -gt 0 ]; then
echo "Found $FRAGMENTS changelog fragment(s), collecting..."
node scripts/collect-changelog.mjs
else
echo "No changelog fragments found, skipping collection"
fi
- name: Version and commit
id: version
run: |
node scripts/version-and-commit.mjs \
--bump-type "${{ github.event.inputs.bump_type }}" \
--description "${{ github.event.inputs.description }}" \
--working-dir rust \
--mode manual
- name: Build release
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
working-directory: rust
run: cargo build --release
- name: Create GitHub Release
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
node scripts/create-github-release.mjs \
--release-version "${{ steps.version.outputs.new_version }}" \
--repository "${{ github.repository }}" \
--prefix "rust-"