Add template app distribution workflow - #36571
Conversation
Supersedes dotnet#36136. Adds a manually triggered workflow that builds the MAUI templates into tester apps for Android, iOS, Mac Catalyst, and Windows, in both blank and sample-content variants. The dry-run path (publish=false) uploads directly-installable GitHub artifacts; publish=true optionally ships to Google Play test tracks and TestFlight via fastlane, gated on the protected template-app-distribution environment secrets. The original PR produced store-shaped artifacts that testers could not install or launch. This version fixes each platform so the uploaded artifact runs on a tester device with no extra tooling: - Android: build a signed, directly-installable APK (v2+v3) for the dry-run path instead of an AAB, which cannot be sideloaded. - Windows: publish self-contained (SelfContained=true plus WindowsAppSDKSelfContained=true) so testers do not need a matching .NET or WindowsAppSDK runtime installed. - iOS: the dry-run now builds an arm64 iOS Simulator app (dotnet build -r iossimulator-arm64, with -p:UseMonoRuntime=false for the net11 CoreCLR build) that actually launches; the device path stays gated behind ad-hoc signing secrets. - Mac Catalyst: pin the arm64-native RID, zip the .app with ditto instead of Compress-Archive (which strips the exec bit and framework symlinks), and re-sign the bundle ad-hoc inside-out so it launches on macOS 15+ and macOS 26 without a Code Signature Invalid SIGKILL. All four artifacts were validated end-to-end from CI output (installed and launched, not just built). See .github/scripts/template-app-distribution/README.md for per-platform tester install steps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 92e506ca-933d-4c5b-ab06-62cf471e259c
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36571Or
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36571" |
|
Azure Pipelines: 1 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new manually-triggered “Template App Distribution” GitHub Actions workflow plus supporting scripts to (1) pack the MAUI templates, (2) generate per-platform apps (blank + sample), (3) produce sideloadable artifacts for dry-runs, and (4) optionally publish to Google Play / TestFlight using fastlane behind a protected environment.
Changes:
- Adds
.github/workflows/template-app-distribution.ymlwith a prepare → dry-run-build / publish job split and a platform/variant matrix. - Adds PowerShell scripts under
.github/scripts/template-app-distribution/to resolve the source ref + SDK, pack templates, generate apps, and build platform-appropriate distributables. - Adds a dedicated fastlane bundle (Gemfile/Gemfile.lock + Fastfile) and a README documenting artifact types and tester install steps.
Reviewed changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| .github/workflows/template-app-distribution.yml | New workflow_dispatch pipeline to build and (optionally) publish template-based tester apps across platforms/variants. |
| .github/scripts/template-app-distribution/Resolve-SourceRef.ps1 | Resolves/validates source_ref and enforces trust rules for publish=true. |
| .github/scripts/template-app-distribution/Resolve-DotNetSdk.ps1 | Resolves SDK version (global.json) and derives the netX.Y TFM used by matrix generation. |
| .github/scripts/template-app-distribution/Prepare-Matrix.ps1 | Builds the variant/platform matrix and default app identifiers/bundle IDs. |
| .github/scripts/template-app-distribution/Pack-Templates.ps1 | Builds/packs Microsoft.Maui.Templates into a local nupkg for dotnet new install. |
| .github/scripts/template-app-distribution/New-TemplateApp.ps1 | Generates an app from the packed templates and normalizes app identity/version settings. |
| .github/scripts/template-app-distribution/Install-AppleSigningAssets.ps1 | Installs Apple certs/profiles into a temporary keychain and exports signing env vars. |
| .github/scripts/template-app-distribution/Build-TemplateApp.ps1 | Builds platform-specific outputs (APK/AAB, iOS IPA + simulator .app zip, Mac Catalyst .pkg + .app zip, Windows self-contained zip). |
| .github/scripts/template-app-distribution/README.md | Tester-facing documentation for what artifacts are produced and how to install/run them. |
| .github/scripts/template-app-distribution/fastlane/Gemfile | Pins fastlane dependency for publish lanes. |
| .github/scripts/template-app-distribution/fastlane/Gemfile.lock | Bundler lockfile for deterministic fastlane installs in CI. |
| .github/scripts/template-app-distribution/fastlane/Fastfile | Implements the fastlane lanes used to upload to Google Play / TestFlight. |
The iOS dry-run only produced a Simulator .app.zip, so testers reported there was no .ipa to install on a real iPhone/iPad. Add a best-effort unsigned ios-arm64 device build wrapped as a Payload/*.app .ipa, which testers can install via AltStore/Sideloadly (re-signed with their own Apple ID). The Simulator app is still uploaded as an additional artifact for Mac-only smoke testing, and the device IPA build is wrapped in try/catch so a failure never regresses the existing Simulator artifact. A directly-installable device build still requires the secret-gated ad-hoc IPA or TestFlight publish path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 92e506ca-933d-4c5b-ab06-62cf471e259c
iOS dry-run now emits an installable
|
| Check | Result |
|---|---|
| Archive layout | Payload/MauiTemplateBlank.app/… at root ✓ |
| Binary | Mach-O 64-bit executable arm64 ✓ |
| Platform | LC_BUILD_VERSION platform 2 (iOS device, not simulator) ✓ |
DTPlatformName |
iphoneos ✓ |
| Embedded profile | none — so no app-store/Beta profile to reject (the original 0xe800801f error) ✓ |
| Signature | not signed at all → AltStore/Sideloadly re-signs ✓ |
Both blank and sample iOS jobs now produce MauiTemplate*.app.ipa (device) + MauiTemplate*.app.zip (Simulator).
…late args via env - Build-TemplateApp.ps1: Write-Base64File now trims surrounding whitespace/newlines before [Convert]::FromBase64String (secrets often carry a trailing newline, which otherwise throws and breaks keystore/cert materialization). Matches the existing trim pattern used for the Android keystore later in the same script. - template-app-distribution.yml: pin all 6 actions/checkout@v7 -> @v4 to match the repo standard and avoid a runtime 'unresolved action' failure (v7 does not exist). - template-app-distribution.yml: pass matrix.templateArgsJson to New-TemplateApp.ps1 via a TEMPLATE_ARGS_JSON env var ($env:TEMPLATE_ARGS_JSON) in both the dry-run and publish jobs, instead of single-quote-interpolating it into the PowerShell command text. Removes the quoting/command-injection vector if the JSON ever contains a single quote. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
Addressed the Copilot review feedback in
All 5 review threads resolved. Ready for re-review.
|
- README: iOS dry-run artifact is "one or two files" (unsigned device .ipa is best-effort, so it may be absent, leaving only the Simulator .app.zip). - Header: TEMPLATE_APP_ANDROID_KEY_PASSWORD is Optional (defaults to the keystore password when unset), not Required. - Pre-flight validation no longer hard-requires MAC_INSTALLER_CERTIFICATE_* for maccatalyst: Install-AppleSigningAssets.ps1 accepts an installer identity from any imported cert (the main IOS_CERTIFICATE p12 can already contain one), so requiring the dedicated secret was a false-negative. - App Store Connect private-key check message now says "iOS/Mac Catalyst publishing" (the step runs for both platforms). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
Addressed the latest Copilot re-review in
All six threads addressed and resolved. Ready for another look, @copilot.
|
… codes - actions/upload-artifact@v7 -> @v4 (v7 does not exist; @v4 is the repo standard) in both the dry-run and publish jobs. - Pack-Templates.ps1: throw when `dotnet build`/`dotnet pack` fail. With $ErrorActionPreference='Stop', native-command failures still don't throw in PowerShell, so an earlier failing build could otherwise fall through to a stale package. Matches the explicit $LASTEXITCODE checks in Build-TemplateApp.ps1. - New-TemplateApp.ps1: throw when `dotnet new install` or `dotnet new <template>` fail, for the same reason. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial code review — PR #36571
Findings
⚠️ Error Handling — Configured Apple sideload signing failures are converted to a successful upload of the non-installable store artifact. 2/3 reviewers after dispute.⚠️ Logic and Correctness — A missing Android APK discovery result silently aliases the non-installable AAB as the sideload artifact. 3/3 reviewers after dispute.⚠️ Error Handling — An AAB publish failure uploads the previous successful APK binlog, leaving the failure without useful diagnostics. 2/3 reviewers + repo domain specialist.
Test coverage
No automated test files were added for this manually dispatched workflow. This review does not assess CI status.
Prior review status
Existing top-level reviews, inline comments, and issue comments were reconciled; previously addressed findings were not duplicated.
Method: 3 independent reviewers with adversarial consensus + repo domain specialist.
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial code review — PR #36571 (re-review)
New findings
⚠️ Logic and Correctness — Custom variant values containing$are interpreted as regex replacement tokens and can corrupt the generated project. 2/3 reviewers + repo domain specialist.⚠️ Error Handling — A TestFlight processing timeout is reported as success even though Fastlane has not distributed the build to external groups. 2/3 reviewers + repo domain specialist.⚠️ Logic and Correctness — The iOS dry-run discards a valid device IPA when simulator artifact discovery fails. 3/3 reviewers after dispute.
Test coverage
No automated test files were added for this manually dispatched workflow. This review does not assess CI status.
Prior review status
The three findings in review 4910509745 remain on this unchanged head and were deliberately not duplicated.
Method: 3 independent reviewers with adversarial consensus + repo domain specialist.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
@PureWeen addressed all six latest findings in eac732d: configured Apple signing failures now fail, missing APKs fail, AABs get a separate binlog, template replacements preserve literal dollar signs, TestFlight processing timeouts fail, and device IPAs survive missing simulator artifacts. Added focused Pester coverage (10 passing) and validated workflow/Ruby/PowerShell syntax. This is ready for re-review — thanks! |
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial code review — PR #36571
Findings
⚠️ Error Handling — A Developer ID provisioning profile without its certificate is silently ignored, causing the non-launchable store package to be emitted as sideload output. 3/3 reviewers after dispute.⚠️ Error Handling — Custom variants can omit requiredtemplate/projectNamefields and fail later with a misleading missing-project error. 3/3 reviewers after dispute.⚠️ Error Handling — A TestFlight beta-review conflict reports success although requested external groups do not receive the build. 2/3 reviewers.⚠️ Testing — Several new regression tests inspect source text rather than exercise guarded behavior. 2/3 reviewers + repo domain specialist.
Test coverage
The new Pester suite covers several helpers, but the four posted regression scenarios are source-text assertions rather than executable coverage of their target paths.
Prior review status
Nine earlier inline findings were reconciled against eac732d and were not duplicated; the corresponding fixes are present at the current head.
Method: 3 independent reviewers with adversarial consensus + repo domain specialist.
Validate paired Developer ID assets and required custom variant metadata. Fail TestFlight review conflicts when external distribution was requested, and replace source-text assertions with executable fake-tool regression tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
@PureWeen addressed all four follow-up findings in df5c379: paired Developer ID validation, required custom variant metadata, external-group TestFlight conflict failure, and executable artifact/Fastlane regression tests. All focused tests pass and the threads are resolved. This is ready for re-review — thanks! |
PureWeen
left a comment
There was a problem hiding this comment.
Adversarial review
Test coverage: The new suite exercises behavior through fake tools and a Fastlane harness rather than source-text matching, but it is not invoked by the workflow.
Prior review status: Rechecked 13 existing agent review threads (10 resolved, 3 unresolved) and did not duplicate prior feedback.
Methodology: 3 independent reviewers with adversarial consensus + repo domain specialist.
Run the behavioral suite before matrix preparation, pin trusted checkouts to the tested SHA, and upload dedicated iOS ad-hoc and Mac Catalyst Developer ID binlogs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: d00747b7-96f3-4e7a-8dfb-e3a48db04b2d
|
@PureWeen addressed the latest test-gating and secondary-binlog feedback in |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 13 changed files in this pull request and generated no new comments.
Suppressed comments (1)
.github/workflows/template-app-distribution.yml:67
- The workflow restricts
GITHUB_TOKENpermissions tocontents: read, but it later usesactions/upload-artifact@v4(e.g. the dry-run upload). Uploading artifacts requiresactions: write; with the current permissions block, artifact upload steps will fail due to insufficient token permissions.
permissions:
contents: read
|
Reviewed the suppressed Copilot permission note; no change is needed. |
Note
Are you waiting for the changes in this PR to be merged?
It would be very helpful if you could test the resulting artifacts from this PR and let us know in a comment if this change resolves your issue. Thank you!
Summary
Supersedes #36136.
Adds a manually triggered Template App Distribution workflow that builds the MAUI templates into tester apps for Android, iOS, Mac Catalyst, and Windows, in both blank and sample-content variants. The dry-run path (
publish=false) uploads installable or sideloadable GitHub artifacts;publish=trueoptionally ships to Google Play test tracks and TestFlight via fastlane, gated on the protectedtemplate-app-distributionenvironment secrets.Why this supersedes #36136
#36136 produced store-shaped artifacts that testers could not install or launch. Reported failures: Android had no APK, Windows showed an "install the .NET runtime" screen, iOS failed with
0xe800801f Attempted to install a Beta profile without the proper entitlement, and Mac Catalyst crashed on launch withEXC_CRASH (SIGKILL - Code Signature Invalid).This version provides a usable test path for every platform:
.aabwas produced (cannot be sideloaded)SelfContained=true+WindowsAppSDKSelfContained=true).app/ TestFlight-profile IPA installs nowhere.pkg/ Rosetta x64 build thenSIGKILL Code Signature Invalid.apponmacos-26, zip withditto, and ad-hoc re-sign inside-out so it launches on current macOS runners and test machinesCurrent .NET 11 Apple toolchain
Current .NET 11 Apple workloads require Xcode 26.6 with the iOS/macOS 26.5 SDK. The previous
macos-15jobs selected Xcode 26.3 / SDK 26.2 and failed withMT0180andMT4162. Both iOS and Mac Catalyst jobs now run onmacos-26and select Xcode 26.6.Full-SHA dry runs also reset PowerShell's native exit status after expected Git trust probes, preventing a successfully resolved source SHA from being reported as a failed prepare job.
Validation
Workflow run 31022037238 passed all eight blank/sample jobs for Android, iOS, Mac Catalyst, and Windows. Its Apple logs confirmed Xcode 26.6 and SDK 26.5.
Artifacts were then validated end-to-end from CI output, not only built:
adb install, launched, rendered, and remained alive.The sample-content app also exposed a separate template-source seed initialization defect on iOS 26.5. That source issue is fixed and empirically validated across iOS 26.5, iOS 18.5, Android 16, and Mac Catalyst in #37122; it is not a distribution-workflow failure.
See
.github/scripts/template-app-distribution/README.mdfor per-platform tester install steps, including the AltStore/Sideloadly requirement for the unsigned iOS device IPA.Setup still needed before
publish=trueworksThe
template-app-distributionGitHub Environment and its signing / store secrets must be created indotnet/maui. Dry-run artifact builds run without any secrets.Note on scope
Unlike #36136 this PR does not touch
src/Templates/src/templates/maui-mobile/MauiApp.1.csproj; that change was an incidental package-version edit from an older base and is now stale relative tomain(which has since moved those versions forward via #36074 and #35608). The workflow does not require it./cc @PureWeen @jfversluis
🔍 This PR was prepared by GitHub Copilot CLI on behalf of @kubaflo, standing in for @jfversluis while he is away. All four platform artifacts were launched, not just built.