399fd24f4d
NuGet ignores our service-level USERPROFILE override (it uses SHGetKnownFolderPath which returns LocalSystem's profile regardless of the env var) and installs packages to C:\Windows\system32\configsystemprofile\.nuget\packages. 32-bit MSBuild then can't find them because the Win32 File System Redirector silently shunts system32 paths to SysWOW64 for 32-bit processes -> NETSDK1064 'package not found'. NUGET_PACKAGES is NuGet's highest-priority package-folder override and puts everything outside system32, so both restore and build agree on paths and the redirector never gets involved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
293 lines
13 KiB
YAML
293 lines
13 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'
|
|
# Force NuGet to install + resolve packages outside C:\Windows\System32\.
|
|
# NuGet's UserProfile lookup uses SHGetKnownFolderPath which ignores our
|
|
# USERPROFILE override and returns the LocalSystem profile when the runner
|
|
# service runs as LocalSystem. 32-bit MSBuild then hits Win32 File System
|
|
# Redirector and "loses" packages -> NETSDK1064. NUGET_PACKAGES short-
|
|
# circuits the lookup and keeps everything outside system32.
|
|
NUGET_PACKAGES: 'C:\actions-home\.nuget\packages'
|
|
|
|
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: Build (restore + compile)
|
|
# `msbuild /restore` runs restore and build in the same MSBuild process,
|
|
# which keeps the NuGet package cache + project.assets.json + assembly
|
|
# resolution consistent (avoids NETSDK1064 from a mismatch between a
|
|
# standalone `nuget restore` and the later build).
|
|
run: msbuild OutlookAddin.sln /restore /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: 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: Build Release (restore + compile)
|
|
run: msbuild OutlookAddin.sln /restore /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
|
|
)"
|