Skip to content

Add template app distribution workflow - #36136

Closed
jfversluis wants to merge 31 commits into
mainfrom
jfversluis/template-app-distribution
Closed

Add template app distribution workflow#36136
jfversluis wants to merge 31 commits into
mainfrom
jfversluis/template-app-distribution

Conversation

@jfversluis

Copy link
Copy Markdown
Member

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

Adds a manually triggered template app distribution workflow that builds the latest MAUI templates into tester apps for Android, iOS, Mac Catalyst, and Windows.

The workflow can generate blank and sample-content template apps, upload build artifacts, and optionally publish Android/iOS/Mac Catalyst builds to Google Play test tracks and TestFlight using fastlane.

Value

This lets us distribute accessibility test apps directly from the templates in this repository instead of keeping a separate sample app repository in sync.

Setup needed before publish runs work

The workflow still needs the template-app-distribution GitHub Environment and its signing/store secrets created in dotnet/maui. Dry-run artifact builds can run without those secrets.

/cc @PureWeen

jfversluis and others added 24 commits June 23, 2026 10:41
Adds a manually triggered workflow that packs local MAUI templates, generates variant apps, builds Android/iOS packages, and publishes through fastlane for store testing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Derive app versions and identifiers automatically, reduce manual workflow inputs, and remove unnecessary iOS signing secrets by generating the keychain password and discovering the imported signing identity.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an early publish-job preflight that reports missing store signing secrets before expensive setup/build steps, and directs dry-run testing to publish=false.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keeps dry-run builds independent from store signing secrets while preserving early validation for publish=true runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Removes conditional TargetFrameworks entries from generated template apps so single-platform dry-run and publish jobs do not restore unrelated workloads such as MacCatalyst during iOS builds.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Select the newest installed Xcode that has both macOS and iPhoneOS SDKs before building iOS template apps.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Select Xcode by highest installed iPhoneOS SDK and ensure SDK convenience symlinks exist on hosted macOS runners.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use SDK-bundled workload manifests so dry-run and publish builds do not pull an iOS workload newer than the hosted runner Xcode SDK.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Keep normal workload manifest updates for Android while pinning iOS to SDK-bundled manifests for hosted runner Xcode compatibility.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Allow template app publishing to pass AndroidSigningStoreType when the upload keystore is PKCS12 or another explicit keystore type.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Make the Google Play release status configurable so first uploads to draft Play apps can use draft while established apps keep completed by default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid showing unresolved matrix placeholders when the dry-run matrix job is skipped during publish runs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Install Mac Catalyst provisioning profiles with the macOS profile extension and pass the profile UUID to MSBuild so the Mac signing task can locate the profile during package publishing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use fastlane's macOS app_platform value when uploading Mac Catalyst PKG packages to TestFlight.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Name Mac Catalyst artifacts as macOS, suppress avoidable Git/Xcode log noise, disable overeager Mac entitlement validation for App Store profiles, and update the sample template SQLite bundle dependency to avoid NuGet audit warnings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid publish binlog artifacts, use validated immutable source SHAs for build jobs, pin fastlane dependencies, and harden publishing ref validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use conditional job-name expressions so skipped matrix jobs show static names instead of raw matrix placeholders.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Avoid raw matrix expressions on skipped workflow jobs; GitHub still appends matrix values to expanded jobs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Treat release/* branches as trusted publish sources, while still requiring tags and SHAs to be reachable from trusted branches.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@jfversluis
jfversluis requested a review from PureWeen June 25, 2026 15:03
@github-actions

Copy link
Copy Markdown
Contributor

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.sh | bash -s -- 36136

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/maui/main/eng/scripts/get-maui-pr.ps1) } 36136"

@github-actions github-actions Bot added the area-infrastructure CI, Maestro / Coherency, upstream dependencies/versions label Jun 25, 2026
@jfversluis

Copy link
Copy Markdown
Member Author

/azp run maui-pr

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines successfully started running 1 pipeline(s).

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial code review — PR #36136

Methodology: 3 independent reviewers (different models) + adversarial consensus (3/3 → include at highest severity; 2/3 → include at lower severity; 1/3 → dispute round with 2 adjudicators). Inline placement validated against the head commit. This is a code-only review — CI status is out of scope. Posted as a COMMENT (never approve/request-changes; that's a human call).

Overall: a few things worth addressing before merge. The trust model itself is sound — all three reviewers independently verified that an untrusted source_ref cannot reach the secret-scoped publish job (Resolve-SourceRef.ps1 -Publish throws on untrusted refs, failing prepare, which publish depends on; checkouts pin to the resolved source_sha, so no TOCTOU).

Must-fix

  • Unpinned GitHub Actions in the secret-scoped publish job (template-app-distribution.yml, inline) — floating tags including third-party ruby/setup-ruby@v1 in the job that holds signing/store secrets. Repo convention pins every action to a full commit SHA.

Should-fix

  • ⚠️ -replace $-substitution corrupts generated csproj (New-TemplateApp.ps1, inline) — XML-escaping doesn't neutralize $ back-references in the replacement string.
  • ⚠️ prepare job can't see Environment-scoped vars.* (template-app-distribution.yml, inline) — silent fallback to default IDs → provisioning-profile mismatch at publish.
  • ⚠️ Unchecked native dotnet exit codes (Pack-Templates.ps1 / New-TemplateApp.ps1, inline) — build/pack failures masked by downstream guards.

Design-level / nits (lower confidence)

  • 💡 Matrix values interpolated inline into pwsh run: blocks rather than passed via env: (the pattern the workflow already uses for inputs.source_ref). Requires repo-variable write access, so not remotely exploitable — consistency / defense-in-depth. (3/3 on facts)
  • 💡 publish never asserts needs.prepare.outputs.trusted == 'true' — it relies on prepare throwing. An explicit if: gate would keep the trust boundary structural across future refactors. (1/3)
  • 💡 source_ref builds template content against the released MAUI workload, not framework binaries from source_ref — the input name could mislead; worth a doc note. (1/3)
  • 💡 SQLitePCLRaw.bundle_green 2.1.10→2.1.11 in MauiApp.1.csproj is unrelated to this workflow — ideally a separate commit. (2/3)

Considered and dropped: the throwaway keychain password passed on the security argv — on an ephemeral single-tenant runner this isn't a meaningful exposure (both dispute adjudicators discarded it).

Tests

No automated tests added; reasonable here — this is CI/workflow + PowerShell glue that's inherently hard to unit-test. The $-substitution and exit-code items above are the parts most amenable to a small Pester test if you want regression coverage.


- name: Setup Ruby and fastlane
if: ${{ matrix.platform == 'android' || matrix.platform == 'ios' || matrix.platform == 'maccatalyst' }}
uses: ruby/setup-ruby@v1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security / Supply chain — This job runs with environment: template-app-distribution and signing/store secrets, yet ruby/setup-ruby@v1 (third-party, floating tag) — along with actions/checkout@v7, actions/setup-dotnet@v5, actions/setup-java@v5, and actions/upload-artifact@v7 elsewhere in this file — is pinned to a mutable tag. This repo's convention is to pin every action to a full commit SHA (see the existing workflows). A retagged or compromised action would execute in the job where store credentials are live. Recommend pinning all uses: to full commit SHAs.

Flagged by: 3/3 reviewers

$content = $content.Remove($match.Index, $match.Length).Insert($match.Index, $replacement)
}

$content = $content -replace "<ApplicationTitle>[^<]+</ApplicationTitle>", "<ApplicationTitle>$(ConvertTo-XmlEscaped $DisplayName)</ApplicationTitle>"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Correctness[SecurityElement]::Escape() (via ConvertTo-XmlEscaped) does not neutralize $, which is special in a PowerShell -replace replacement string ($1, $&, $$). If a DisplayName / ApplicationId / version value that originates from TEMPLATE_APP_VARIANTS_JSON contains e.g. $& or $1, the replacement silently expands the back-reference and corrupts the generated csproj. The same applies to the ApplicationId, ApplicationDisplayVersion, and ApplicationVersion replacements immediately below. Recommend [regex]::Replace(...) with a MatchEvaluator, or doubling $$$ in the replacement.

Flagged by: 3/3 reviewers

$env:NUGET_PACKAGES = $NuGetPackages

Write-Host "Building MAUI templates from $templatesProject"
dotnet build -t:Rebuild $templatesProject -p:PackageVersion=$PackageVersion -p:GenerateCgManifest=false

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Error handling$ErrorActionPreference = 'Stop' does not make PowerShell throw when a native command exits non-zero, so a failed dotnet build here (and dotnet pack on the next line) is not caught — execution continues to the Get-ChildItem / if (-not $package) guard, which masks the real build failure behind a generic "package not found". The same pattern appears with dotnet new install / dotnet new in New-TemplateApp.ps1. Recommend checking $LASTEXITCODE after each native call (as Invoke-DotNetPublish already does elsewhere).

Flagged by: 2/3 reviewers

id: matrix
shell: pwsh
env:
TEMPLATE_APP_IDENTIFIER_PREFIX: ${{ vars.TEMPLATE_APP_IDENTIFIER_PREFIX }}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Config / correctness — The prepare job declares no environment:, so any of these vars.TEMPLATE_APP_* that are defined as Environment-scoped variables (rather than repo/org-level) resolve to empty here. The matrix then silently falls back to default bundle/application IDs, surfacing as a provisioning-profile mismatch only later at publish time. The header comments describe these as "variables" without noting they must be repo- or org-level to be visible in this job. Recommend documenting the required scope, or resolving IDs in an environment-gated step.

Flagged by: 3/3 reviewers

Copilot AI review requested due to automatic review settings July 1, 2026 12:50
Use the selected template source branch's NuGet.config when installing MAUI workloads and when generating template apps so preview branches can resolve branch-specific packages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a manually triggered GitHub Actions workflow to build distributable apps from the MAUI templates (blank + sample) across Android, iOS, Mac Catalyst, and Windows, with optional publishing via fastlane to Google Play test tracks and TestFlight. The PR title and description match the implementation and include the required NOTE block plus setup guidance for the protected environment/secrets.

Changes:

  • Adds a new template-app-distribution workflow that builds template-generated apps (dry-run artifact build or publish mode).
  • Introduces supporting PowerShell scripts to pack templates, generate projects, build packages, and install Apple signing assets, plus a pinned fastlane bundle for store uploads.
  • Updates the mobile template to consume SQLitePCLRaw.bundle_green 2.1.11.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/Templates/src/templates/maui-mobile/MauiApp.1.csproj Bumps SQLitePCLRaw.bundle_green dependency version used by the mobile template.
.github/workflows/template-app-distribution.yml New workflow to build/upload (and optionally publish) generated template apps across platforms.
.github/scripts/template-app-distribution/Resolve-SourceRef.ps1 Validates and resolves the source_ref, with trust checks for publish runs.
.github/scripts/template-app-distribution/Resolve-DotNetSdk.ps1 Resolves the .NET SDK and derives a netX.Y TFM from global.json.
.github/scripts/template-app-distribution/Prepare-Matrix.ps1 Builds a variant/platform matrix and derives app identifiers/display names.
.github/scripts/template-app-distribution/Pack-Templates.ps1 Builds/packs the local template nupkg used for dotnet new generation.
.github/scripts/template-app-distribution/New-TemplateApp.ps1 Generates a template app, patches identifiers/versions, and writes a per-app global.json.
.github/scripts/template-app-distribution/Install-AppleSigningAssets.ps1 Installs Apple certs/profiles into a temp keychain for signing iOS/Mac Catalyst builds.
.github/scripts/template-app-distribution/Build-TemplateApp.ps1 Publishes platform-specific artifacts (AAB/IPA/PKG/Windows zip) with optional signing.
.github/scripts/template-app-distribution/fastlane/Gemfile Pins fastlane version for reproducible publish runs.
.github/scripts/template-app-distribution/fastlane/Gemfile.lock Locked fastlane dependency graph for CI runs.
.github/scripts/template-app-distribution/fastlane/Fastfile Fastlane lanes for Google Play uploads and TestFlight uploads with release notes support.

Comment on lines +34 to +38
Write-Host "Building MAUI templates from $templatesProject"
dotnet build -t:Rebuild $templatesProject -p:PackageVersion=$PackageVersion -p:GenerateCgManifest=false

Write-Host "Packing MAUI templates with PackageVersion=$PackageVersion"
dotnet pack $templatesProject -p:PackageVersion=$PackageVersion -p:GenerateCgManifest=false -o $OutputPath
Comment on lines +95 to +96
Write-Host "Installing template package $TemplatePackagePath"
dotnet new install $TemplatePackagePath
Comment on lines +103 to +105
$dotnetNewArgs = @("new", $Template, "-n", $ProjectName, "-o", $projectDir, "--framework", $DotNetTfm, "--no-restore") + $templateArgs
Write-Host "Creating project: dotnet $($dotnetNewArgs -join ' ')"
& dotnet @dotnetNewArgs
Comment on lines +45 to +48
function Write-Base64File([string]$Base64Value, [string]$Path) {
$bytes = [Convert]::FromBase64String($Base64Value)
[System.IO.File]::WriteAllBytes($Path, $bytes)
}
# - TEMPLATE_APP_ANDROID_KEYSTORE_BASE64
# - TEMPLATE_APP_ANDROID_KEYSTORE_PASSWORD
# - TEMPLATE_APP_ANDROID_KEY_ALIAS
# - TEMPLATE_APP_ANDROID_KEY_PASSWORD
Enable the existing implicit XAML namespace compatibility flag when generated net11 apps need it, and use CoreCLR for net11+ Apple targets.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 1, 2026 13:24
Use NativeAOT for net11 iOS store packages and provide explicit Mac Catalyst runtime identifiers for net11 CoreCLR publishing.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Comment on lines +34 to +38
Write-Host "Building MAUI templates from $templatesProject"
dotnet build -t:Rebuild $templatesProject -p:PackageVersion=$PackageVersion -p:GenerateCgManifest=false

Write-Host "Packing MAUI templates with PackageVersion=$PackageVersion"
dotnet pack $templatesProject -p:PackageVersion=$PackageVersion -p:GenerateCgManifest=false -o $OutputPath
Comment on lines +148 to +159
Write-Host "Installing template package $TemplatePackagePath"
dotnet new install $TemplatePackagePath

$templateArgs = @()
if (-not [string]::IsNullOrWhiteSpace($TemplateArgsJson)) {
$templateArgs = @(ConvertFrom-Json $TemplateArgsJson | ForEach-Object { [string]$_ })
}

$dotnetNewArgs = @("new", $Template, "-n", $ProjectName, "-o", $projectDir, "--framework", $DotNetTfm, "--no-restore") + $templateArgs
Write-Host "Creating project: dotnet $($dotnetNewArgs -join ' ')"
& dotnet @dotnetNewArgs

Comment on lines +89 to +95
& security create-keychain -p $keychainPassword $keychainPath
& security set-keychain-settings -lut 21600 $keychainPath
& security unlock-keychain -p $keychainPassword $keychainPath

$existingKeychains = & security list-keychains -d user | ForEach-Object { $_.Trim().Trim('"') }
& security list-keychains -d user -s $keychainPath @existingKeychains
& security import $certificatePath -k $keychainPath -P $certificatePassword -T /usr/bin/codesign -T /usr/bin/security
Pass the net11 Mac Catalyst RuntimeIdentifiers list through MSBuild escaping so it remains a single property value.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 1, 2026 13:49
Use a single App Store-compatible Mac Catalyst RID for net11 publish builds and tolerate TestFlight beta-review conflicts after successful uploads.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.

Comment on lines +34 to +38
Write-Host "Building MAUI templates from $templatesProject"
dotnet build -t:Rebuild $templatesProject -p:PackageVersion=$PackageVersion -p:GenerateCgManifest=false

Write-Host "Packing MAUI templates with PackageVersion=$PackageVersion"
dotnet pack $templatesProject -p:PackageVersion=$PackageVersion -p:GenerateCgManifest=false -o $OutputPath
Comment on lines +148 to +149
Write-Host "Installing template package $TemplatePackagePath"
dotnet new install $TemplatePackagePath
Comment on lines +157 to +158
Write-Host "Creating project: dotnet $($dotnetNewArgs -join ' ')"
& dotnet @dotnetNewArgs
Comment on lines +45 to +48
function Write-Base64File([string]$Base64Value, [string]$Path) {
$bytes = [Convert]::FromBase64String($Base64Value)
[System.IO.File]::WriteAllBytes($Path, $bytes)
}
Comment on lines +58 to +63
function Invoke-DotNetPublish([string[]]$Arguments, [string]$Description) {
& dotnet @Arguments
if ($LASTEXITCODE -ne 0) {
throw "$Description failed with exit code $LASTEXITCODE."
}
}
Apply a finite fastlane processing timeout and treat post-upload TestFlight processing timeouts as successful uploads so template distribution runs do not hang indefinitely.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 1, 2026 16:05
Recognize fastlane's BuildWatcher timeout text as a post-upload TestFlight processing timeout so Mac Catalyst uploads do not fail after the configured wait expires.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.

Comment on lines +54 to +56
permissions:
contents: read

Comment on lines +34 to +38
Write-Host "Building MAUI templates from $templatesProject"
dotnet build -t:Rebuild $templatesProject -p:PackageVersion=$PackageVersion -p:GenerateCgManifest=false

Write-Host "Packing MAUI templates with PackageVersion=$PackageVersion"
dotnet pack $templatesProject -p:PackageVersion=$PackageVersion -p:GenerateCgManifest=false -o $OutputPath
Comment on lines +67 to +69
$entry = "`t<key>$Key</key>`r`n`t<false/>`r`n"
$plistContent = $plistContent -replace "(?m)^</dict>", "$entry</dict>"
Set-Content -Path $Path -Value $plistContent -Encoding utf8
Comment on lines +45 to +48
function Write-Base64File([string]$Base64Value, [string]$Path) {
$bytes = [Convert]::FromBase64String($Base64Value)
[System.IO.File]::WriteAllBytes($Path, $bytes)
}

@PureWeen PureWeen left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adversarial code review — PR #36136 (re-review of the 7 new commits)

Methodology: 3 independent reviewers (different models) + adversarial consensus (3/3 → highest severity; 2/3 → lower severity; 1/3 → dispute round). This round targets what changed since my 2026-07-01 review (f4898c9742ef07): net11 support, NuGet.config sourcing, TestFlight timeout handling, and exit-code checks. Code-only review — CI status is out of scope. Findings already covered by my earlier review or the Copilot reviewer are not repeated.

Net-new findings

⚠️ Regression / Config — net11 MacCatalyst forced to x64-only · Build-TemplateApp.ps1:218 · 2/3
The matrix default RID is empty (runtimeIdentifier: "") on the macos-15 (Apple Silicon) runner, so the net11+ branch forces -r maccatalyst-x64 → an Intel-only artifact. The net10 path (empty RID, no -r) instead falls through to the template's Release default of maccatalyst-x64;maccatalyst-arm64 (universal), so net11 testers on Apple Silicon get a Rosetta-only build and lose native arm64. arm64-only is not the fix — the template csproj explicitly notes the Mac App Store rejects arm64-only builds. See inline for the suggested fix.

⚠️ Error Handling — TestFlight errors swallowed as success · Fastfile:114 · 3/3
The upload rescue treats both a review conflict and a broad processing+timeout substring match as a successful upload, so an unrelated App Store Connect failure can turn the secret-scoped publish job green with nothing delivered to testers. See inline.

Lower-severity notes (💡)

  • Add-ProjectDefineConstant — new $-substitution sink (New-TemplateApp.ps1). Same class as my already-posted -replace finding, but a distinct net-new location: [regex]::Replace($Content, pattern, "<DefineConstants>$value…", 1) treats $1/$&/$`/$' in the replacement string specially. $(DefineConstants) is safe, and it isn't triggerable with today's template (low likelihood), but the no-existing-property path is literal/safe, so the inconsistency is worth closing — escape via $value -replace '\$','$$' or use a literal string replace. (2/3)
  • integer_environment parses non-decimal radix (Fastfile). Integer(value) honors prefixes: "010"8 (silent octal), "0900"ArgumentError (aborts the publish), and 0x/0b are parsed. A zero-padded TEMPLATE_APP_TESTFLIGHT_WAIT_TIMEOUT_SECONDS misbehaves. Fix: Integer(value, 10). (2/3, verified empirically)

Status of my prior findings (all still open)

  • Unpinned GitHub Actions — still floating (@v7/@v5, incl. third-party ruby/setup-ruby@v1) in the secret-scoped publish job. Open.
  • ⚠️ -replace $-substitution on the Application* rewrites (New-TemplateApp.ps1) — unchanged. Open.
  • ⚠️ Unchecked native exit codespartially addressed: dotnet workload install now checks $LASTEXITCODE, but dotnet build/dotnet pack (Pack-Templates.ps1) and dotnet new install/dotnet new (New-TemplateApp.ps1) are still unchecked. Open.
  • ⚠️ prepare job has no environment: (Environment-scoped vars.* invisible during matrix prep) — unchanged. Open.

Notes

  • Verified not bugs: the iOS-vs-MacCatalyst NativeAOT asymmetry (iOS requires AOT/no-JIT; MacCatalyst on CoreCLR without AOT is correct) and the net11 detection / implicit-xmlns XAML heuristics.
  • No new automated tests (workflow + PowerShell/Ruby glue is inherently hard to unit-test); reasonable, though the Integer() base-10 and $-substitution items are the parts most amenable to a small Pester/unit check.
  • Prior reviews already on the PR: my 2026-07-01 review + 4 rounds of the Copilot reviewer. This round intentionally avoids repeating them.

if (-not [string]::IsNullOrWhiteSpace($RuntimeIdentifier)) {
$arguments += @("-r", $RuntimeIdentifier)
} elseif ($useNet11OrLater) {
$arguments += @("-r", "maccatalyst-x64")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Regression / Config Impact — For net11+ MacCatalyst with no explicit RID (the matrix default is runtimeIdentifier: "", and the runner is macos-15, i.e. Apple Silicon/arm64), this forces -r maccatalyst-x64, producing an Intel-only artifact. The net10 path (empty RID, no -r) instead falls through to the template's Release default maccatalyst-x64;maccatalyst-arm64 (universal), so net11 testers on Apple Silicon get a Rosetta-only build and lose native arm64.

Note arm64-only is not a valid fix — the template csproj comment states the Mac App Store rejects arm64-only builds. If a RID must be forced for net11, prefer the universal pair via -p:RuntimeIdentifiers=maccatalyst-x64;maccatalyst-arm64 (or omit -r and let the Release RuntimeIdentifiers apply). If x64-only is deliberate here, a short comment explaining why would help.

Flagged by: 2/3 reviewers


begin
upload_to_testflight(upload_options)
rescue => error

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Error Handling — This rescue => error treats both "another build is in review" (testflight_review_conflict?) and a processing-wait timeout (testflight_processing_timeout?) as a successful upload. The timeout classifier is a broad substring match ("processing" AND any of timeout/timed out/waited), so an unrelated App Store Connect error whose text merely contains those tokens — e.g. "Error processing request … the request timed out" — is swallowed and the publish job goes green even though no build reached testers. Because this runs in the secret-scoped publish path, a genuine delivery failure is silently hidden.

Consider narrowing the timeout match to fastlane's specific "still processing after wait" signal (e.g. require the BuildWatcher exceeded phrasing rather than the generic processing+timeout combination), and always log the full underlying error even when downgrading to success so a masked real failure stays diagnosable.

Flagged by: 3/3 reviewers

@kubaflo

kubaflo commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

🔍 Superseded by #36571 — closing this one.

The install failures testers reported with the artifacts from this PR — Android had no APK, Windows showed a "install the .NET runtime" screen, iOS failed with 0xe800801f (beta-profile entitlement), and Mac Catalyst crashed with EXC_CRASH (SIGKILL - Code Signature Invalid) — all traced back to the uploaded artifacts being store-shaped rather than directly installable.

#36571 rebuilds the same workflow on top of current main with those fixes, and validates every platform end-to-end from CI output (installed and launched, not just built):

  • Android — signed, directly-installable APK (v2+v3) instead of an .aab
  • Windowsself-contained publish, so no separate runtime install is needed
  • iOS — dry-run builds an arm64 iOS Simulator app that actually launches (the device path stays gated behind ad-hoc signing secrets)
  • Mac Catalystarm64-native .app, zipped with ditto, ad-hoc re-signed inside-out so it launches on macOS 15+ / 26

It also drops the incidental MauiApp.1.csproj package-version edit from here, which is now stale relative to main (versions moved forward via #36074 / #35608) and isn't needed by the workflow.

Comment posted by GitHub Copilot CLI on behalf of @kubaflo, standing in for @jfversluis while he is away.

@kubaflo kubaflo closed this Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-infrastructure CI, Maestro / Coherency, upstream dependencies/versions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants