This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OutlookAddin/.gitea/workflows/build.yml
T
chaim e6c2b099d0 fix(ci): use VS Pro MSBuild (requires Office workload) instead of Build Tools
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>
2026-05-11 15:59:50 +00:00

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
)"