Zero-Click Releases: Building a Revit Plugin Pipeline with GitHub Actions

Eliminate manual deployment pain with automated Revit add-in deployment to Design Automation using RAPS and GitHub Actions

#github-actions #ci-cd #design-automation #revit #automation #devops
Dmytro Yemelianov - Author
RAPS Team
Autodesk Expert Elite โ€ข APS Developer

Validated: Design Automation workflow claims are tested via automated benchmarks. View latest results

From 20 Minutes to 0 Minutes: The Manual Tax

In our previous exploration of AEC development pain points, we discussed โ€œThe Manual Taxโ€ โ€” the hidden cost of manual deployments. Today, we kill the manual process entirely.

Deploying a Revit Add-in to Design Automation usually involves:

  1. Compiling the DLLs locally (hopefully in Release mode)
  2. Zipping them up correctly (did you remember the dependencies?)
  3. Getting a new authentication token (expired again?)
  4. Uploading the AppBundle (please work this timeโ€ฆ)
  5. Creating a new Activity alias (production or staging?)

If you do this manually, you will eventually upload a debug build to production. Hereโ€™s how to automate it using RAPS and GitHub Actions.

The Zero-Click Workflow

Weโ€™ll create a pipeline that runs on every git push to the main branch, transforming your โ€œtime to deployโ€ from 20 minutes of clicking to 0 minutes.

flowchart LR subgraph "Developer" A[git push] --> B[Go Get Coffee] end subgraph "GitHub Actions" C[Checkout] --> D[Setup .NET] D --> E[Build Release] E --> F[Install RAPS] F --> G[raps auth login] G --> H[raps da bundle create] H --> I[raps da appbundle update] I --> J[raps da activity update] end subgraph "APS Design Automation" K[AppBundle Updated] L[Activity Ready] M[Production Alias Set] end subgraph "Notifications" N[Slack: Deployed!] end B -.->|Triggered| C J --> K K --> L L --> M M --> N style A fill:#4c9aff style N fill:#36b37e

1. Define the RAPS Configuration

First, ensure your repository has a raps.toml defining your AppBundle structure. This tells the CLI which DLLs to bundle and how to structure your deployment.

[appbundle]
name = "my-revit-plugin"
engine = "Autodesk.Revit+2024"
description = "Automated Revit processing plugin"

[appbundle.bundle]
main_dll = "MyPlugin.dll"
dependencies = [
    "MyPlugin.Shared.dll",
    "Newtonsoft.Json.dll"
]

[appbundle.activity]
command_line = "$(engine.path)\\\\revit.exe /i $(args[inputFile].path) /al $(appbundles[{appbundle.name}].path)"

2. The GitHub Action

Create .github/workflows/deploy.yml:

name: Deploy to APS Design Automation

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

jobs:
  build-and-deploy:
    runs-on: windows-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: '6.0.x'
    
    - name: Restore dependencies
      run: dotnet restore
    
    - name: Build Release
      run: dotnet build --configuration Release --no-restore
    
    - name: Install RAPS
      run: |
        curl -fsSL https://rapscli.xyz/install.sh | sh
        echo "$HOME/.cargo/bin" >> $GITHUB_PATH
    
    - name: Bundle & Deploy
      if: github.ref == 'refs/heads/main'
      env:
        APS_CLIENT_ID: ${{ secrets.APS_CLIENT_ID }}
        APS_CLIENT_SECRET: ${{ secrets.APS_CLIENT_SECRET }}
      run: |
        # Authenticate with APS
        raps auth login --client-id $APS_CLIENT_ID --client-secret $APS_CLIENT_SECRET
        
        # Create the bundle (handles zipping automatically)
        raps da:bundle create --path ./bin/Release/net48
        
        # Update the AppBundle in the cloud and alias it to 'prod'
        raps da:appbundle update my-revit-plugin --alias prod
        
        # Update the Activity to use the new bundle version
        raps da:activity update my-revit-plugin --alias prod
    
    - name: Test Deployment
      if: github.ref == 'refs/heads/main'
      run: |
        # Run a simple test to verify the deployment
        raps da:workitem run my-revit-plugin \
          --input-file https://developer.api.autodesk.com/oss/v2/signedresources/test.rvt \
          --output-format ifc

3. Advanced: Multi-Environment Deployment

For more sophisticated workflows, deploy to different environments based on the branch:

name: Multi-Environment Deploy

on:
  push:
    branches: [ "main", "staging", "develop" ]

jobs:
  deploy:
    runs-on: windows-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Determine Environment
      id: env
      run: |
        if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
          echo "environment=production" >> $GITHUB_OUTPUT
          echo "alias=prod" >> $GITHUB_OUTPUT
        elif [[ "${{ github.ref }}" == "refs/heads/staging" ]]; then
          echo "environment=staging" >> $GITHUB_OUTPUT
          echo "alias=staging" >> $GITHUB_OUTPUT
        else
          echo "environment=development" >> $GITHUB_OUTPUT
          echo "alias=dev" >> $GITHUB_OUTPUT
        fi
    
    # ... build steps ...
    
    - name: Deploy to ${{ steps.env.outputs.environment }}
      env:
        APS_CLIENT_ID: ${{ secrets.APS_CLIENT_ID }}
        APS_CLIENT_SECRET: ${{ secrets.APS_CLIENT_SECRET }}
        ENVIRONMENT: ${{ steps.env.outputs.environment }}
        ALIAS: ${{ steps.env.outputs.alias }}
      run: |
        raps auth login --client-id $APS_CLIENT_ID --client-secret $APS_CLIENT_SECRET
        
        # Deploy with environment-specific alias
        raps da:bundle create --path ./bin/Release/net48
        raps da:appbundle update my-revit-plugin --alias $ALIAS
        raps da:activity update my-revit-plugin --alias $ALIAS
        
        echo "โœ… Deployed to $ENVIRONMENT environment with alias '$ALIAS'"

Deployment Time Tax

Minutes spent on manual deployment vs. automated pipeline

Manual Deployment 20 min/release
Zero-Click Release 0.1 min/release

Why This Transforms Your Workflow

Before: The Manual Tax

  • 20 minutes per deployment
  • High error rate (debug builds, missing files)
  • Context switching from development to deployment tasks
  • Inconsistent deployment process across team members
  • No audit trail of what was deployed when

After: Zero-Click Releases

  • 0 minutes active deployment time
  • Zero human errors (automated building and bundling)
  • Consistent process across all environments
  • Complete audit trail in GitHub Actions logs
  • Immediate rollback capability via git revert

Advanced Features

Automatic Version Tagging

Add automatic semantic versioning to your releases:

- name: Generate Version
  id: version
  run: |
    VERSION=$(date +%Y.%m.%d)-$(git rev-parse --short HEAD)
    echo "version=$VERSION" >> $GITHUB_OUTPUT
    echo "Generated version: $VERSION"

- name: Tag AppBundle Version
  run: |
    raps da:appbundle update my-revit-plugin \
      --alias prod \
      --version ${{ steps.version.outputs.version }} \
      --description "Auto-deployed from commit ${{ github.sha }}"

Slack/Teams Notifications

Get notified when deployments complete:

- name: Notify Success
  if: success()
  uses: 8398a7/action-slack@v3
  with:
    status: success
    text: "โœ… Revit plugin deployed to production successfully!"
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

- name: Notify Failure
  if: failure()
  uses: 8398a7/action-slack@v3
  with:
    status: failure
    text: "โŒ Revit plugin deployment failed. Check GitHub Actions for details."
  env:
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}

Integration Testing

Test your deployed plugin automatically:

- name: Integration Test
  run: |
    # Test with a small sample file
    WORKITEM_ID=$(raps da:workitem run my-revit-plugin \
      --input-file ${{ secrets.TEST_FILE_URL }} \
      --output-format ifc \
      --wait \
      --format json | jq -r '.id')
    
    # Verify the workitem completed successfully
    STATUS=$(raps da:workitem status $WORKITEM_ID --format json | jq -r '.status')
    
    if [[ "$STATUS" != "succeeded" ]]; then
      echo "โŒ Integration test failed. WorkItem status: $STATUS"
      exit 1
    fi
    
    echo "โœ… Integration test passed. Plugin is working correctly."

Security Best Practices

Environment Variables

Store sensitive data in GitHub Secrets:

  • APS_CLIENT_ID - Your Autodesk app client ID
  • APS_CLIENT_SECRET - Your Autodesk app client secret
  • SLACK_WEBHOOK - Slack notification webhook (optional)
  • TEST_FILE_URL - URL to your test Revit file (optional)

Least Privilege Access

Create a dedicated APS app for CI/CD with minimal required scopes:

# Create a CI-specific app with limited permissions
raps auth create-app \
  --name "ci-deployment-app" \
  --scopes "code:all bucket:create bucket:read data:read data:write"

Monitoring and Observability

Track your deployment success rate:

- name: Record Deployment Metrics
  if: always()
  run: |
    # Send deployment metrics to your monitoring system
    curl -X POST "${{ secrets.METRICS_ENDPOINT }}" \
      -H "Content-Type: application/json" \
      -d '{
        "deployment": "revit-plugin",
        "status": "${{ job.status }}",
        "duration": "${{ steps.deploy.outputs.duration }}",
        "commit": "${{ github.sha }}",
        "branch": "${{ github.ref_name }}"
      }'

Troubleshooting Common Issues

Build Failures

- name: Debug Build Issues
  if: failure()
  run: |
    echo "=== Build Logs ==="
    cat build.log
    
    echo "=== Assembly References ==="
    raps debug assembly-info ./bin/Release/net48/*.dll

Authentication Problems

- name: Test Authentication
  run: |
    # Verify auth is working
    raps auth test || {
      echo "โŒ Authentication failed"
      echo "Check APS_CLIENT_ID and APS_CLIENT_SECRET secrets"
      exit 1
    }

Bundle Size Issues

- name: Check Bundle Size
  run: |
    BUNDLE_SIZE=$(du -h ./bundle.zip | cut -f1)
    echo "Bundle size: $BUNDLE_SIZE"
    
    # Warn if bundle is over 100MB
    if [[ $(du -b ./bundle.zip | cut -f1) -gt 104857600 ]]; then
      echo "โš ๏ธ Warning: Bundle size ($BUNDLE_SIZE) is large. Consider optimizing dependencies."
    fi

The Bottom Line

With this automation pipeline:

  • You push code โ†’ You go get coffee โ†’ Your plugin is deployed
  • Zero manual steps in your deployment process
  • Consistent, repeatable deployments across all environments
  • Complete audit trail and instant rollback capability
  • Integration testing ensures quality before production

The โ€œManual Taxโ€ is expensive. Automation pays for itself after the first few deployments.

Coming Up Next

In our next article, weโ€™ll explore why we chose Rust over Node.js for building RAPS, and how this decision enables processing 5GB+ model files that would crash traditional JavaScript tooling.


Part of our โ€œAEC DevOps Revolutionโ€ series. Because your deployment process shouldnโ€™t require a PhD in clicking buttons.