summaryrefslogtreecommitdiff
path: root/.github
diff options
context:
space:
mode:
Diffstat (limited to '.github')
-rw-r--r--.github/pull_request_template.md37
-rw-r--r--.github/workflows/.gitattributes1
-rw-r--r--.github/workflows/build.yaml13
-rw-r--r--.github/workflows/check-pr.yaml21
-rw-r--r--.github/workflows/cla.yaml4
-rw-r--r--.github/workflows/qatest.yaml598
-rw-r--r--.github/workflows/tag-release.yaml7
7 files changed, 672 insertions, 9 deletions
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md
new file mode 100644
index 0000000000..5fe8870337
--- /dev/null
+++ b/.github/pull_request_template.md
@@ -0,0 +1,37 @@
+## Description
+
+<!--
+Describe what this PR changes or adds. Include any relevant context or motivation.
+-->
+
+## Related Issues
+
+- [ ] **Please link to a relevant GitHub issue for additional context.**
+ - **Bug Fix:** Link to an issue that includes reproduction steps and testing guidance.
+ - **Feature/Enhancement:** Link to an issue with a write-up, rationale, and requirements.
+
+Issue Link: <!-- e.g., closes #123 or relates to #456 -->
+
+---
+
+## Checklist
+
+Please ensure the following before requesting review:
+
+- [ ] I have provided a clear title and detailed description for this pull request.
+- [ ] If useful, I have included media such as screenshots and video to show off my changes.
+- [ ] The PR is linked to a relevant issue with sufficient context.
+- [ ] I have tested the changes locally and verified they work as intended.
+- [ ] All new and existing tests pass.
+- [ ] Code follows the project's style guidelines.
+- [ ] Documentation has been updated if needed.
+- [ ] Any dependent changes have been merged and published in downstream modules
+- [ ] I have reviewed the [contributing guidelines](https://github.com/secondlife/viewer/blob/develop/CONTRIBUTING.md).
+
+---
+
+## Additional Notes
+
+<!--
+Add any other information, screenshots, or suggestions for reviewers here.
+-->
diff --git a/.github/workflows/.gitattributes b/.github/workflows/.gitattributes
new file mode 100644
index 0000000000..6dc1cca42a
--- /dev/null
+++ b/.github/workflows/.gitattributes
@@ -0,0 +1 @@
+qatest.yaml -text eol=crlf
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 2cb94c9c90..4bf2af644a 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -42,8 +42,8 @@ jobs:
needs: setup
strategy:
matrix:
- runner: [windows-large, macos-12-xl]
- configuration: ${{ fromJSON(needs.setup.outputs.configurations) }}
+ runner: ${{ fromJson((github.ref_type == 'tag' && startsWith(github.ref, 'refs/tags/Second_Life')) && '["windows-large","macos-15-xlarge"]' || '["windows-latest","macos-15"]') }}
+ configuration: ${{ fromJson(needs.setup.outputs.configurations) }}
runs-on: ${{ matrix.runner }}
outputs:
viewer_channel: ${{ steps.build.outputs.viewer_channel }}
@@ -64,7 +64,7 @@ jobs:
# autobuild-package.xml.
AUTOBUILD_VCS_INFO: "true"
AUTOBUILD_VSVER: "170"
- DEVELOPER_DIR: "/Applications/Xcode_14.0.1.app/Contents/Developer"
+ DEVELOPER_DIR: "/Applications/Xcode_16.1.app/Contents/Developer"
# Ensure that Linden viewer builds engage Bugsplat.
BUGSPLAT_DB: ${{ needs.setup.outputs.bugsplat_db }}
build_coverity: false
@@ -218,8 +218,10 @@ jobs:
prefix=${ba[0]}
if [ "$prefix" == "project" ]; then
IFS='_' read -ra prj <<< "${ba[1]}"
+ prj_str="${prj[*]}"
# uppercase first letter of each word
- export viewer_channel="Second Life Project ${prj[*]^}"
+ capitalized=$(echo "$prj_str" | awk '{for (i=1; i<=NF; i++) $i = toupper(substr($i,1,1)) substr($i,2); print}')
+ export viewer_channel="Second Life Project $capitalized"
elif [[ "$prefix" == "release" || "$prefix" == "main" ]];
then
export viewer_channel="Second Life Release"
@@ -304,7 +306,7 @@ jobs:
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
needs: build
- runs-on: windows-large
+ runs-on: windows-latest
steps:
- name: Sign and package Windows viewer
if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID
@@ -455,7 +457,6 @@ jobs:
prerelease: true
generate_release_notes: true
target_commitish: ${{ github.sha }}
- previous_tag: release
append_body: true
fail_on_unmatched_files: true
files: |
diff --git a/.github/workflows/check-pr.yaml b/.github/workflows/check-pr.yaml
new file mode 100644
index 0000000000..a5cee9157c
--- /dev/null
+++ b/.github/workflows/check-pr.yaml
@@ -0,0 +1,21 @@
+name: Check PR
+
+on:
+ pull_request:
+ types: [opened, edited, reopened, synchronize]
+
+permissions:
+ contents: read
+
+jobs:
+ check-description:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Check PR description
+ uses: actions/github-script@v7
+ with:
+ script: |
+ const description = context.payload.pull_request.body || '';
+ if (description.trim().length < 20) {
+ core.setFailed("❌ PR description is too short. Please provide at least 20 characters.");
+ }
diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml
index 3f4bf21864..7866f943b5 100644
--- a/.github/workflows/cla.yaml
+++ b/.github/workflows/cla.yaml
@@ -13,7 +13,7 @@ jobs:
steps:
- name: CLA Assistant
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
- uses: secondlife-3p/contributor-assistant@v2
+ uses: secondlife-3p/contributor-assistant@v2.6.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PERSONAL_ACCESS_TOKEN: ${{ secrets.SHARED_CLA_TOKEN }}
@@ -23,4 +23,4 @@ jobs:
path-to-signatures: signatures.json
remote-organization-name: secondlife
remote-repository-name: cla-signatures
- allowlist: callum@mbp.localdomain
+ allowlist: callum@mbp.localdomain,rye@lindenlab.com
diff --git a/.github/workflows/qatest.yaml b/.github/workflows/qatest.yaml
new file mode 100644
index 0000000000..4e10900441
--- /dev/null
+++ b/.github/workflows/qatest.yaml
@@ -0,0 +1,598 @@
+name: Run QA Test # Runs automated tests on self-hosted QA machines
+
+permissions:
+ contents: read
+
+on:
+ workflow_run:
+ workflows: ["Build"]
+ types:
+ - completed
+ workflow_dispatch:
+ inputs:
+ build_id:
+ description: 'Build workflow run ID (e.g. For github.com/secondlife/viewer/actions/runs/1234567890 the ID is 1234567890)'
+ required: true
+ default: '14806728332'
+
+jobs:
+ debug-workflow:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Debug Workflow Variables
+ run: |
+ echo "Workflow Conclusion: ${{ github.event.workflow_run.conclusion }}"
+ echo "Workflow Head Branch: ${{ github.event.workflow_run.head_branch }}"
+ echo "Workflow Run ID: ${{ github.event.workflow_run.id }}"
+ echo "Head Commit Message: ${{ github.event.workflow_run.head_commit.message }}"
+ echo "GitHub Ref: ${{ github.ref }}"
+ echo "GitHub Ref Name: ${{ github.ref_name }}"
+ echo "GitHub Event Name: ${{ github.event_name }}"
+ echo "GitHub Workflow Name: ${{ github.workflow }}"
+
+ install-viewer-and-run-tests:
+ concurrency:
+ group: ${{ github.workflow }}-${{ matrix.runner }}
+ cancel-in-progress: false # Prevents cancellation of in-progress jobs
+
+ strategy:
+ matrix:
+ include:
+ - os: windows
+ runner: qa-windows-atlas
+ artifact: Windows-installer
+ install-path: 'C:\viewer-automation-main'
+ - os: windows
+ runner: qa-dan-asus
+ artifact: Windows-installer
+ install-path: 'C:\viewer-automation-main'
+ - os: mac
+ runner: qa-mac-atlas
+ artifact: macOS-installer
+ install-path: '$HOME/Documents/viewer-automation'
+ fail-fast: false
+
+ runs-on: [self-hosted, "${{ matrix.runner }}"]
+ # Run test only on successful builds of Second_Life_X branches or on manual dispatch
+ if: >
+ (github.event_name == 'workflow_run' &&
+ github.event.workflow_run.conclusion == 'success' &&
+ startsWith(github.event.workflow_run.head_branch, 'Second_Life')) ||
+ github.event_name == 'workflow_dispatch'
+
+ steps:
+ # Windows-specific steps
+ - name: Set Build ID
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ if ("${{ github.event_name }}" -eq "workflow_dispatch") {
+ echo "BUILD_ID=${{ github.event.inputs.build_id }}" | Out-File -FilePath $env:GITHUB_ENV -Append
+ echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.inputs.build_id }}/artifacts" | Out-File -FilePath $env:GITHUB_ENV -Append
+ } else {
+ echo "BUILD_ID=${{ github.event.workflow_run.id }}" | Out-File -FilePath $env:GITHUB_ENV -Append
+ echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.workflow_run.id }}/artifacts" | Out-File -FilePath $env:GITHUB_ENV -Append
+ }
+
+ - name: Temporarily Allow PowerShell Scripts (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ Set-ExecutionPolicy RemoteSigned -Scope Process -Force
+
+ - name: Verify viewer-automation-main Exists (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ if (-Not (Test-Path -Path '${{ matrix.install-path }}')) {
+ Write-Host '❌ Error: viewer-automation folder not found on runner!'
+ exit 1
+ }
+ Write-Host '✅ viewer-automation folder is provided.'
+
+ - name: Verify viewer-automation-main is Up-To-Date (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ continue-on-error: true
+ run: |
+ cd ${{ matrix.install-path }}
+ Write-Host "Checking for repository updates..."
+
+ # Check if .git directory exists
+ if (Test-Path -Path ".git") {
+ try {
+ # Save local changes instead of discarding them
+ git stash push -m "Automated stash before update $(Get-Date)"
+ Write-Host "Local changes saved (if any)"
+
+ # Update the repository
+ git pull
+ Write-Host "✅ Repository updated successfully"
+
+ # Try to restore local changes if any were stashed
+ $stashList = git stash list
+ if ($stashList -match "Automated stash before update") {
+ try {
+ git stash pop
+ Write-Host "✅ Local changes restored successfully"
+ } catch {
+ Write-Host "⚠️ Conflict when restoring local changes"
+ # Save the conflicted state in a new branch for later review
+ $branchName = "conflict-recovery-$(Get-Date -Format 'yyyyMMdd-HHmmss')"
+ git checkout -b $branchName
+ Write-Host "✅ Created branch '$branchName' with conflicted state"
+
+ # For test execution, revert to a clean state
+ git reset --hard HEAD
+ Write-Host "✅ Reset to clean state for test execution"
+ }
+ }
+ } catch {
+ Write-Host "⚠️ Could not update repository: $_"
+ Write-Host "Continuing with existing files..."
+ }
+ } else {
+ Write-Host "⚠️ Not a Git repository, using existing files"
+ }
+
+ - name: Verify Python Installation (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ try {
+ $pythonVersion = (python --version)
+ Write-Host "✅ Python found: $pythonVersion"
+ } catch {
+ Write-Host "❌ Error: Python not found in PATH. Please install Python on this runner."
+ exit 1
+ }
+
+ - name: Setup Python Virtual Environment (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
+ cd ${{ matrix.install-path }}
+
+ if (-Not (Test-Path -Path ".venv")) {
+ Write-Host "Creating virtual environment..."
+ python -m venv .venv
+ } else {
+ Write-Host "Using existing virtual environment"
+ }
+
+ # Direct environment activation to avoid script execution issues
+ $env:VIRTUAL_ENV = "$PWD\.venv"
+ $env:PATH = "$env:VIRTUAL_ENV\Scripts;$env:PATH"
+
+ # Install dependencies
+ if (Test-Path -Path "requirements.txt") {
+ Write-Host "Installing dependencies from requirements.txt..."
+ pip install -r requirements.txt
+
+ # Install Playwright browsers - add this line
+ Write-Host "Installing Playwright browsers..."
+ python -m playwright install
+ } else {
+ pip install outleap requests behave playwright
+ # Install Playwright browsers - add this line
+ Write-Host "Installing Playwright browsers..."
+ python -m playwright install
+ }
+
+ - name: Fetch & Download Installer Artifact (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ $BUILD_ID = "${{ env.BUILD_ID }}"
+ $ARTIFACTS_URL = "${{ env.ARTIFACTS_URL }}"
+
+ # Fetch the correct artifact URL
+ $response = Invoke-RestMethod -Headers @{Authorization="token ${{ secrets.GITHUB_TOKEN }}" } -Uri $ARTIFACTS_URL
+ $ARTIFACT_NAME = ($response.artifacts | Where-Object { $_.name -eq "${{ matrix.artifact }}" }).archive_download_url
+
+ if (-Not $ARTIFACT_NAME) {
+ Write-Host "❌ Error: ${{ matrix.artifact }} artifact not found!"
+ exit 1
+ }
+
+ Write-Host "✅ Artifact found: $ARTIFACT_NAME"
+
+ # Secure download path
+ $DownloadPath = "$env:TEMP\secondlife-build-$BUILD_ID"
+ New-Item -ItemType Directory -Path $DownloadPath -Force | Out-Null
+ $InstallerPath = "$DownloadPath\installer.zip"
+
+ # Download the ZIP
+ Invoke-WebRequest -Uri $ARTIFACT_NAME -Headers @{Authorization="token ${{ secrets.GITHUB_TOKEN }}"} -OutFile $InstallerPath
+
+ # Ensure download succeeded
+ if (-Not (Test-Path $InstallerPath)) {
+ Write-Host "❌ Error: Failed to download ${{ matrix.artifact }}.zip"
+ exit 1
+ }
+
+ # Set the path for other steps
+ echo "DOWNLOAD_PATH=$DownloadPath" | Out-File -FilePath $env:GITHUB_ENV -Append
+
+ - name: Extract Installer & Locate Executable (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ $BUILD_ID = "${{ env.BUILD_ID }}"
+ $ExtractPath = "${{ env.DOWNLOAD_PATH }}"
+ $InstallerZip = "$ExtractPath\installer.zip"
+
+ # Print paths for debugging
+ Write-Host "Extract Path: $ExtractPath"
+ Write-Host "Installer ZIP Path: $InstallerZip"
+
+ # Verify ZIP exists before extracting
+ if (-Not (Test-Path $InstallerZip)) {
+ Write-Host "❌ Error: ZIP file not found at $InstallerZip!"
+ exit 1
+ }
+
+ Write-Host "✅ ZIP file exists and is valid. Extracting..."
+
+ Expand-Archive -Path $InstallerZip -DestinationPath $ExtractPath -Force
+
+ # Find installer executable
+ $INSTALLER_PATH = (Get-ChildItem -Path $ExtractPath -Filter '*.exe' -Recurse | Select-Object -First 1).FullName
+
+ if (-Not $INSTALLER_PATH -or $INSTALLER_PATH -eq "") {
+ Write-Host "❌ Error: No installer executable found in the extracted files!"
+ Write-Host "📂 Extracted Files:"
+ Get-ChildItem -Path $ExtractPath -Recurse | Format-Table -AutoSize
+ exit 1
+ }
+
+ Write-Host "✅ Installer found: $INSTALLER_PATH"
+ echo "INSTALLER_PATH=$INSTALLER_PATH" | Out-File -FilePath $env:GITHUB_ENV -Append
+
+ - name: Install Second Life (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ # Windows - Use Task Scheduler to bypass UAC
+ $action = New-ScheduledTaskAction -Execute "${{ env.INSTALLER_PATH }}" -Argument "/S"
+ $principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
+ $task = New-ScheduledTask -Action $action -Principal $principal
+ Register-ScheduledTask -TaskName "SilentSLInstaller" -InputObject $task -Force
+ Start-ScheduledTask -TaskName "SilentSLInstaller"
+
+ - name: Wait for Installation to Complete (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ Write-Host "Waiting for the Second Life installer to finish..."
+ do {
+ Start-Sleep -Seconds 5
+ $installerProcess = Get-Process | Where-Object { $_.Path -eq "${{ env.INSTALLER_PATH }}" }
+ } while ($installerProcess)
+
+ Write-Host "✅ Installation completed!"
+
+ - name: Cleanup After Installation (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ # Cleanup Task Scheduler Entry
+ Unregister-ScheduledTask -TaskName "SilentSLInstaller" -Confirm:$false
+ Write-Host "✅ Task Scheduler entry removed."
+
+ # Delete Installer ZIP
+ $DeletePath = "${{ env.DOWNLOAD_PATH }}\installer.zip"
+
+ Write-Host "Checking if installer ZIP exists: $DeletePath"
+
+ # Ensure the ZIP file exists before trying to delete it
+ if (Test-Path $DeletePath) {
+ Remove-Item -Path $DeletePath -Force
+ Write-Host "✅ Successfully deleted: $DeletePath"
+ } else {
+ Write-Host "⚠️ Warning: ZIP file does not exist, skipping deletion."
+ }
+
+ - name: Run QA Test Script (Windows)
+ if: matrix.os == 'windows'
+ shell: pwsh
+ run: |
+ Write-Host "Running QA Test script on Windows runner: ${{ matrix.runner }}..."
+ cd ${{ matrix.install-path }}
+
+ # Activate virtual environment
+ Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process -Force
+ $env:VIRTUAL_ENV = "$PWD\.venv"
+ $env:PATH = "$env:VIRTUAL_ENV\Scripts;$env:PATH"
+
+ # Set runner name as environment variable
+ $env:RUNNER_NAME = "${{ matrix.runner }}"
+
+ # Run the test script
+ python runTests.py
+
+ # Mac-specific steps
+ - name: Set Build ID (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
+ echo "BUILD_ID=${{ github.event.inputs.build_id }}" >> $GITHUB_ENV
+ echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.inputs.build_id }}/artifacts" >> $GITHUB_ENV
+ else
+ echo "BUILD_ID=${{ github.event.workflow_run.id }}" >> $GITHUB_ENV
+ echo "ARTIFACTS_URL=https://api.github.com/repos/secondlife/viewer/actions/runs/${{ github.event.workflow_run.id }}/artifacts" >> $GITHUB_ENV
+ fi
+
+ - name: Verify viewer-automation-main Exists (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ if [ ! -d "${{ matrix.install-path }}" ]; then
+ echo "❌ Error: viewer-automation folder not found on runner!"
+ exit 1
+ fi
+ echo "✅ viewer-automation is provided."
+
+ - name: Verify viewer-automation-main is Up-To-Date (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ continue-on-error: true
+ run: |
+ cd ${{ matrix.install-path }}
+ echo "Checking for repository updates..."
+
+ # Check if .git directory exists
+ if [ -d ".git" ]; then
+ # Save local changes instead of discarding them
+ git stash push -m "Automated stash before update $(date)"
+ echo "Local changes saved (if any)"
+
+ # Update the repository
+ git pull || echo "⚠️ Could not update repository"
+ echo "✅ Repository updated (or attempted update)"
+
+ # Try to restore local changes if any were stashed
+ if git stash list | grep -q "Automated stash before update"; then
+ # Try to pop the stash, but be prepared for conflicts
+ if ! git stash pop; then
+ echo "⚠️ Conflict when restoring local changes"
+ # Save the conflicted state in a new branch for later review
+ branch_name="conflict-recovery-$(date +%Y%m%d-%H%M%S)"
+ git checkout -b "$branch_name"
+ echo "✅ Created branch '$branch_name' with conflicted state"
+
+ # For test execution, revert to a clean state
+ git reset --hard HEAD
+ echo "✅ Reset to clean state for test execution"
+ else
+ echo "✅ Local changes restored successfully"
+ fi
+ fi
+ else
+ echo "⚠️ Not a Git repository, using existing files"
+ fi
+
+ - name: Verify Python Installation (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ if command -v python3 &> /dev/null; then
+ PYTHON_VERSION=$(python3 --version)
+ echo "✅ Python found: $PYTHON_VERSION"
+ else
+ echo "❌ Error: Python3 not found in PATH. Please install Python on this runner."
+ exit 1
+ fi
+
+ - name: Setup Python Virtual Environment (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ cd ${{ matrix.install-path }}
+
+ # Create virtual environment if it doesn't exist
+ if [ ! -d ".venv" ]; then
+ echo "Creating virtual environment..."
+ python3 -m venv .venv
+ else
+ echo "Using existing virtual environment"
+ fi
+
+ # Activate virtual environment
+ source .venv/bin/activate
+
+ # Install dependencies
+ if [ -f "requirements.txt" ]; then
+ pip install -r requirements.txt
+ echo "✅ Installed dependencies from requirements.txt"
+
+ # Install Playwright browsers - add this line
+ echo "Installing Playwright browsers..."
+ python -m playwright install
+ else
+ pip install outleap requests behave playwright
+ echo "⚠️ requirements.txt not found, installed basic dependencies"
+
+ # Install Playwright browsers - add this line
+ echo "Installing Playwright browsers..."
+ python -m playwright install
+ fi
+
+ - name: Fetch & Download Installer Artifact (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ # Mac-specific Bash commands
+ response=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -s ${{ env.ARTIFACTS_URL }})
+ ARTIFACT_NAME=$(echo $response | jq -r '.artifacts[] | select(.name=="${{ matrix.artifact }}") | .archive_download_url')
+
+ if [ -z "$ARTIFACT_NAME" ]; then
+ echo "❌ Error: ${{ matrix.artifact }} artifact not found!"
+ exit 1
+ fi
+
+ echo "✅ Artifact found: $ARTIFACT_NAME"
+
+ # Secure download path
+ DOWNLOAD_PATH="/tmp/secondlife-build-${{ env.BUILD_ID }}"
+ mkdir -p $DOWNLOAD_PATH
+ INSTALLER_PATH="$DOWNLOAD_PATH/installer.zip"
+
+ # Download the ZIP
+ curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" -L $ARTIFACT_NAME -o $INSTALLER_PATH
+
+ # Ensure download succeeded
+ if [ ! -f "$INSTALLER_PATH" ]; then
+ echo "❌ Error: Failed to download ${{ matrix.artifact }}.zip"
+ exit 1
+ fi
+
+ # Set the path for other steps
+ echo "DOWNLOAD_PATH=$DOWNLOAD_PATH" >> $GITHUB_ENV
+
+ - name: Extract Installer & Locate Executable (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ EXTRACT_PATH="${{ env.DOWNLOAD_PATH }}"
+ INSTALLER_ZIP="$EXTRACT_PATH/installer.zip"
+
+ # Debug output
+ echo "Extract Path: $EXTRACT_PATH"
+ echo "Installer ZIP Path: $INSTALLER_ZIP"
+
+ # Verify ZIP exists
+ if [ ! -f "$INSTALLER_ZIP" ]; then
+ echo "❌ Error: ZIP file not found at $INSTALLER_ZIP!"
+ exit 1
+ fi
+
+ echo "✅ ZIP file exists and is valid. Extracting..."
+
+ # Extract the ZIP
+ unzip -o "$INSTALLER_ZIP" -d "$EXTRACT_PATH"
+
+ # Find DMG file
+ INSTALLER_PATH=$(find "$EXTRACT_PATH" -name "*.dmg" -type f | head -1)
+
+ if [ -z "$INSTALLER_PATH" ]; then
+ echo "❌ Error: No installer DMG found in the extracted files!"
+ echo "📂 Extracted Files:"
+ ls -la "$EXTRACT_PATH"
+ exit 1
+ fi
+
+ echo "✅ Installer found: $INSTALLER_PATH"
+ echo "INSTALLER_PATH=$INSTALLER_PATH" >> $GITHUB_ENV
+
+ - name: Install Second Life (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ # Mac installation
+ echo "Mounting DMG installer..."
+ MOUNT_POINT="/tmp/secondlife-dmg"
+ mkdir -p "$MOUNT_POINT"
+
+ # Mount the DMG
+ hdiutil attach "$INSTALLER_PATH" -mountpoint "$MOUNT_POINT" -nobrowse
+
+ echo "✅ DMG mounted at $MOUNT_POINT"
+
+ echo "Installing application to default location from DMG..."
+
+ # Find the .app bundle in the DMG
+ APP_PATH=$(find "$MOUNT_POINT" -name "*.app" -type d | head -1)
+
+ if [ -z "$APP_PATH" ]; then
+ echo "❌ Error: No .app bundle found in the mounted DMG!"
+ exit 1
+ fi
+
+ APP_NAME=$(basename "$APP_PATH")
+ DEST_PATH="/Applications/$APP_NAME"
+
+ # Handle existing installation
+ if [ -d "$DEST_PATH" ]; then
+ echo "Found existing installation at: $DEST_PATH"
+ echo "Moving existing installation to trash..."
+
+ # Move to trash instead of force removing
+ TRASH_PATH="$HOME/.Trash/$(date +%Y%m%d_%H%M%S)_$APP_NAME"
+ mv "$DEST_PATH" "$TRASH_PATH" || {
+ echo "⚠️ Could not move to trash, trying direct removal..."
+ rm -rf "$DEST_PATH" || {
+ echo "❌ Could not remove existing installation"
+ echo "Please manually remove: $DEST_PATH"
+ exit 1
+ }
+ }
+
+ echo "✅ Existing installation handled successfully"
+ fi
+
+ # Copy the .app to /Applications
+ echo "Copying app from: $APP_PATH"
+ echo "To destination: /Applications/"
+ cp -R "$APP_PATH" /Applications/
+
+ # Verify the app was copied successfully
+ if [ ! -d "$DEST_PATH" ]; then
+ echo "❌ Error: Failed to install application to /Applications!"
+ exit 1
+ fi
+
+ echo "✅ Application installed successfully to /Applications"
+
+ # Save mount point for cleanup
+ echo "MOUNT_POINT=$MOUNT_POINT" >> $GITHUB_ENV
+
+ - name: Wait for Installation to Complete (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ echo "Waiting for installation to complete..."
+ # Sleep to allow installation to finish (adjust as needed)
+ sleep 30
+ echo "✅ Installation completed"
+
+ - name: Cleanup After Installation (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ # Mac cleanup
+ # Unmount the DMG
+ echo "Unmounting DMG..."
+ hdiutil detach "${{ env.MOUNT_POINT }}" -force
+
+ # Clean up temporary files
+ echo "Cleaning up temporary files..."
+ rm -rf "${{ env.DOWNLOAD_PATH }}"
+ rm -rf "${{ env.MOUNT_POINT }}"
+
+ echo "✅ Cleanup completed"
+
+ - name: Run QA Test Script (Mac)
+ if: matrix.os == 'mac'
+ shell: bash
+ run: |
+ echo "Running QA Test script on Mac runner: ${{ matrix.runner }}..."
+ cd ${{ matrix.install-path }}
+
+ # Activate virtual environment
+ source .venv/bin/activate
+
+ # Set runner name as environment variable
+ export RUNNER_NAME="${{ matrix.runner }}"
+
+ # Run the test script
+ python runTests.py
+
+ # - name: Upload Test Results
+ # if: always()
+ # uses: actions/upload-artifact@v4
+ # with:
+ # name: test-results-${{ matrix.runner }}
+ # path: ${{ matrix.install-path }}/regressionTest/test_results.html
diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml
index 65d1d43a83..24ee2de794 100644
--- a/.github/workflows/tag-release.yaml
+++ b/.github/workflows/tag-release.yaml
@@ -37,7 +37,12 @@ jobs:
- name: Update Tag
uses: actions/github-script@v7.0.1
with:
- github-token: ${{ secrets.GITHUB_TOKEN }}
+ # use a real access token instead of GITHUB_TOKEN default.
+ # required so that the results of this tag creation can trigger the build workflow
+ # https://stackoverflow.com/a/71372524
+ # https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow
+ # this token will need to be renewed anually in January
+ github-token: ${{ secrets.LL_TAG_RELEASE_TOKEN }}
script: |
github.rest.git.createRef({
owner: context.repo.owner,