e6c2b099d0
VS Build Tools 2022 does not ship Microsoft.VisualStudio.Tools.Office.targets (only the full VS IDE editions do), so the VSTO project fails to import them (MSB4226). The runner machine already has VS Pro 2022 with the Office workload installed; switch from microsoft/setup-msbuild@v2 (which picks Build Tools via -latest) to a manual vswhere step that explicitly requires the Office workload. Also removes the temporary filesystem-debug step (root cause was the Win32 File System Redirector pulling system32 paths to SysWOW64 for 32-bit nuget/MSBuild; fixed at the runner level via USERPROFILE redirect, not in the workflow). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
294 lines
12 KiB
YAML
294 lines
12 KiB
YAML
name: Build and deploy OutlookAddin
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
tags: ['v*']
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
env:
|
|
REGISTRY: gitea.dev.marcus-law.co.il
|
|
CDN_IMAGE_NAME: espocrm-extensions/outlook-addin-cdn
|
|
PROVIDER_URL: 'https://platform.dev.marcus-law.co.il/outlook-addin/OutlookAddin.vsto'
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: windows-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Add Office-capable MSBuild to PATH
|
|
shell: powershell
|
|
run: |
|
|
# microsoft/setup-msbuild@v2 picks `vswhere -latest`, which prefers
|
|
# Build Tools 2022. Build Tools has no VSTO targets (MSB4226), so
|
|
# locate a VS installation that explicitly has the Office workload
|
|
# (the one that ships Microsoft.VisualStudio.Tools.Office.targets).
|
|
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
|
$installPath = & $vswhere -products * -requires Microsoft.VisualStudio.Workload.Office -property installationPath | Select-Object -First 1
|
|
if (-not $installPath) { throw "No VS installation with the Office workload found on this runner." }
|
|
$msbuildDir = Join-Path $installPath 'MSBuild\Current\Bin'
|
|
Write-Host "Using MSBuild from $msbuildDir"
|
|
Add-Content -Path $env:GITHUB_PATH -Value $msbuildDir
|
|
|
|
- name: Setup NuGet
|
|
uses: nuget/setup-nuget@v2
|
|
|
|
- name: Restore NuGet
|
|
run: nuget restore OutlookAddin.sln
|
|
|
|
- name: Build
|
|
run: msbuild OutlookAddin.sln /t:Build /p:Configuration=Release /p:Platform="Any CPU" /m
|
|
|
|
- name: Test
|
|
run: dotnet test src/OutlookAddin.Tests/OutlookAddin.Tests.csproj --configuration Release --no-build --logger "trx;LogFileName=test-results.trx"
|
|
|
|
- name: Upload test results
|
|
if: always()
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: test-results
|
|
path: '**/*.trx'
|
|
|
|
publish-clickonce:
|
|
needs: build
|
|
runs-on: windows-latest
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Add Office-capable MSBuild to PATH
|
|
shell: powershell
|
|
run: |
|
|
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
|
$installPath = & $vswhere -products * -requires Microsoft.VisualStudio.Workload.Office -property installationPath | Select-Object -First 1
|
|
if (-not $installPath) { throw "No VS installation with the Office workload found on this runner." }
|
|
Add-Content -Path $env:GITHUB_PATH -Value (Join-Path $installPath 'MSBuild\Current\Bin')
|
|
|
|
- name: Setup NuGet
|
|
uses: nuget/setup-nuget@v2
|
|
|
|
- name: Resolve mage.exe + signtool.exe paths
|
|
id: tools
|
|
shell: powershell
|
|
run: |
|
|
# mage.exe lives under .NET 4.8 Developer Pack. Find any installed copy.
|
|
$mage = Get-ChildItem 'C:\Program Files (x86)\Microsoft SDKs\Windows' -Recurse -Filter mage.exe -ErrorAction SilentlyContinue |
|
|
Sort-Object FullName -Descending | Select-Object -First 1
|
|
if (-not $mage) { throw "mage.exe not found" }
|
|
|
|
# signtool.exe lives under Windows SDK. Pick the highest-version x64 copy.
|
|
$signtool = Get-ChildItem 'C:\Program Files (x86)\Windows Kits\10\bin' -Recurse -Filter signtool.exe -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.FullName -like '*\x64\*' } |
|
|
Sort-Object { [version](Split-Path -Leaf (Split-Path -Parent (Split-Path -Parent $_.FullName))) } -Descending |
|
|
Select-Object -First 1
|
|
if (-not $signtool) { throw "signtool.exe not found" }
|
|
|
|
Write-Host "MAGE: $($mage.FullName)"
|
|
Write-Host "SIGNTOOL: $($signtool.FullName)"
|
|
"MAGE=$($mage.FullName)" | Out-File -Append $env:GITHUB_ENV
|
|
"SIGNTOOL=$($signtool.FullName)" | Out-File -Append $env:GITHUB_ENV
|
|
|
|
- name: Compute version from tag
|
|
id: ver
|
|
shell: powershell
|
|
run: |
|
|
$tag = "${{ github.ref_name }}"
|
|
if ($tag -notmatch '^v(\d+\.\d+\.\d+)$') {
|
|
throw "Tag $tag is not in vMAJOR.MINOR.PATCH form"
|
|
}
|
|
$version = "$($Matches[1]).0"
|
|
"version=$version" | Out-File -Append $env:GITHUB_OUTPUT
|
|
"versionDot=$($Matches[1])" | Out-File -Append $env:GITHUB_OUTPUT
|
|
"versionUscore=$($version -replace '\.','_')" | Out-File -Append $env:GITHUB_OUTPUT
|
|
Write-Host "Version: $version"
|
|
|
|
- name: Ensure code-signing cert in LocalMachine\My
|
|
shell: powershell
|
|
env:
|
|
THUMB: ${{ secrets.CODESIGN_THUMBPRINT }}
|
|
PFX_B64: ${{ secrets.CODESIGN_CERT_PFX_BASE64 }}
|
|
PFX_PWD: ${{ secrets.CODESIGN_CERT_PASSWORD }}
|
|
run: |
|
|
$thumb = $env:THUMB
|
|
$found = Get-ChildItem Cert:\LocalMachine\My -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Thumbprint -eq $thumb }
|
|
if ($found) {
|
|
Write-Host "Cert $thumb already in LocalMachine\My."
|
|
return
|
|
}
|
|
Write-Warning "Cert $thumb missing from LocalMachine\My. Importing from CODESIGN_CERT_PFX_BASE64."
|
|
$pfxBytes = [Convert]::FromBase64String($env:PFX_B64)
|
|
$tmpPfx = Join-Path $env:TEMP "codesign-$([Guid]::NewGuid()).pfx"
|
|
[IO.File]::WriteAllBytes($tmpPfx, $pfxBytes)
|
|
$securePwd = ConvertTo-SecureString -String $env:PFX_PWD -Force -AsPlainText
|
|
Import-PfxCertificate -FilePath $tmpPfx `
|
|
-CertStoreLocation Cert:\LocalMachine\My `
|
|
-Password $securePwd | Out-Null
|
|
Remove-Item -Force $tmpPfx
|
|
Write-Host "Cert imported."
|
|
|
|
- name: Restore NuGet
|
|
run: nuget restore OutlookAddin.sln
|
|
|
|
- name: Build Release
|
|
run: msbuild OutlookAddin.sln /t:Build /p:Configuration=Release /p:Platform="Any CPU" /m
|
|
|
|
# Code signing -- cert in LocalMachine\My (or CurrentUser\My); signtool
|
|
# picks it by thumbprint, the service runs as LocalSystem so the runtime
|
|
# search reaches both stores.
|
|
- name: Sign assemblies
|
|
shell: powershell
|
|
run: |
|
|
$thumb = "${{ secrets.CODESIGN_THUMBPRINT }}"
|
|
& "$env:SIGNTOOL" sign /sha1 $thumb /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 `
|
|
src/OutlookAddin/bin/Release/MarcusLaw.OutlookAddin.dll `
|
|
src/OutlookAddin.Core/bin/Release/net48/MarcusLaw.OutlookAddin.Core.dll `
|
|
src/OutlookAddin.UI/bin/Release/net48/MarcusLaw.OutlookAddin.UI.dll `
|
|
src/OutlookAddinProtocolHandler/bin/Release/net48/OutlookAddinProtocolHandler.exe
|
|
|
|
- name: Publish ClickOnce
|
|
shell: powershell
|
|
run: |
|
|
$version = "${{ steps.ver.outputs.version }}"
|
|
$versionUscore = "${{ steps.ver.outputs.versionUscore }}"
|
|
$thumb = "${{ secrets.CODESIGN_THUMBPRINT }}"
|
|
|
|
$appBin = "src/OutlookAddin/bin/Release"
|
|
$publishDir = "publish"
|
|
$appFilesDir = "$publishDir/Application Files/OutlookAddin_$versionUscore"
|
|
|
|
New-Item -ItemType Directory -Force -Path $appFilesDir | Out-Null
|
|
Copy-Item -Recurse -Force "$appBin/*" $appFilesDir
|
|
|
|
# Application manifest (.dll.manifest)
|
|
& "$env:MAGE" -New Application `
|
|
-ToFile "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" `
|
|
-Name "Marcus-Law OutlookAddin" `
|
|
-Version $version `
|
|
-FromDirectory $appFilesDir `
|
|
-TrustLevel FullTrust
|
|
|
|
& "$env:MAGE" -Sign "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" `
|
|
-CertHash $thumb `
|
|
-TimeStampUri http://timestamp.digicert.com
|
|
|
|
# Deployment manifest (.vsto)
|
|
Copy-Item "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" "$publishDir/OutlookAddin.vsto"
|
|
& "$env:MAGE" -Update "$publishDir/OutlookAddin.vsto" `
|
|
-AppManifest "$appFilesDir/MarcusLaw.OutlookAddin.dll.manifest" `
|
|
-ProviderUrl "$env:PROVIDER_URL" `
|
|
-Publisher "Marcus-Law" `
|
|
-Install true
|
|
|
|
& "$env:MAGE" -Sign "$publishDir/OutlookAddin.vsto" `
|
|
-CertHash $thumb `
|
|
-TimeStampUri http://timestamp.digicert.com
|
|
|
|
# Rename payload files to .deploy (ClickOnce convention).
|
|
Get-ChildItem -Recurse -File "$appFilesDir" |
|
|
Where-Object { $_.Extension -notin '.manifest','.deploy' } |
|
|
ForEach-Object { Rename-Item -LiteralPath $_.FullName -NewName ($_.Name + '.deploy') }
|
|
|
|
- name: Stage protocol handler
|
|
shell: powershell
|
|
run: |
|
|
$stagingDir = "publish/protocol-handler"
|
|
New-Item -ItemType Directory -Force -Path $stagingDir | Out-Null
|
|
Copy-Item -Recurse -Force "src/OutlookAddinProtocolHandler/bin/Release/net48/*" $stagingDir
|
|
Copy-Item -Force "tools/install-protocol.ps1" "$stagingDir/install-protocol.ps1"
|
|
Copy-Item -Force "tools/uninstall-protocol.ps1" "$stagingDir/uninstall-protocol.ps1"
|
|
|
|
- name: Upload publish artifact
|
|
uses: actions/upload-artifact@v4
|
|
with:
|
|
name: clickonce-${{ steps.ver.outputs.versionDot }}
|
|
path: publish/
|
|
|
|
dockerize-and-deploy:
|
|
needs: publish-clickonce
|
|
runs-on: ubuntu-latest
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
env:
|
|
# Set this Gitea Actions secret AFTER the outlook-addin-cdn Coolify app
|
|
# is created. If empty, the trigger step is skipped (image still builds).
|
|
COOLIFY_UUID: ${{ secrets.COOLIFY_UUID }}
|
|
steps:
|
|
- name: Checkout (Dockerfile + nginx.conf)
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Compute version from tag
|
|
id: ver
|
|
run: |
|
|
TAG="${GITHUB_REF##refs/tags/}"
|
|
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "Tag $TAG is not in vMAJOR.MINOR.PATCH form" >&2
|
|
exit 1
|
|
fi
|
|
VERSION="${TAG#v}"
|
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
|
|
|
- name: Download publish artifact
|
|
uses: actions/download-artifact@v4
|
|
with:
|
|
name: clickonce-${{ steps.ver.outputs.version }}
|
|
path: cdn/publish/
|
|
|
|
- name: Login to Gitea Registry
|
|
run: |
|
|
echo "${{ secrets.REGISTRY_PASSWORD }}" | \
|
|
docker login ${{ env.REGISTRY }} \
|
|
-u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
|
|
|
- name: Build and push image
|
|
run: |
|
|
BASE="${{ env.REGISTRY }}/${{ env.CDN_IMAGE_NAME }}"
|
|
docker build \
|
|
-t "${BASE}:latest" \
|
|
-t "${BASE}:${{ steps.ver.outputs.tag }}" \
|
|
-t "${BASE}:build-${{ github.run_number }}" \
|
|
cdn/
|
|
docker push "${BASE}:latest"
|
|
docker push "${BASE}:${{ steps.ver.outputs.tag }}"
|
|
docker push "${BASE}:build-${{ github.run_number }}"
|
|
|
|
- name: Trigger Coolify redeploy
|
|
if: env.COOLIFY_UUID != ''
|
|
run: |
|
|
curl -sf \
|
|
"http://coolify:8080/api/v1/deploy?uuid=${{ env.COOLIFY_UUID }}&force=true" \
|
|
-H "Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}"
|
|
|
|
- name: Notify Mattermost (success)
|
|
if: success()
|
|
env:
|
|
WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK_RELEASES }}
|
|
run: |
|
|
if [ -z "$WEBHOOK" ]; then exit 0; fi
|
|
VERSION="${{ steps.ver.outputs.version }}"
|
|
curl -sf -X POST -H 'Content-Type: application/json' "$WEBHOOK" -d "$(cat <<EOF
|
|
{
|
|
"text": ":package: **OutlookAddin v$VERSION** שוחרר — https://platform.dev.marcus-law.co.il/outlook-addin/OutlookAddin.vsto",
|
|
"channel": "git-verelasim"
|
|
}
|
|
EOF
|
|
)"
|
|
|
|
- name: Notify Mattermost (failure)
|
|
if: failure()
|
|
env:
|
|
WEBHOOK: ${{ secrets.MATTERMOST_WEBHOOK_RELEASES }}
|
|
run: |
|
|
if [ -z "$WEBHOOK" ]; then exit 0; fi
|
|
curl -sf -X POST -H 'Content-Type: application/json' "$WEBHOOK" -d "$(cat <<EOF
|
|
{
|
|
"text": ":warning: OutlookAddin publish ל-${{ github.ref_name }} נכשל — ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}",
|
|
"channel": "git-verelasim"
|
|
}
|
|
EOF
|
|
)"
|