Pack project-mode support - #829
Zach Teutsch (zateutsch) wants to merge 104 commits into
Conversation
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a9a4773d-85be-4cc0-9cba-b5cbd7c7fa3b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Merge parent df86779 while preserving remote recipe-threading changes and stack metadata. Includes current main's image dependency and UI text-attribute updates. Validation: 499 CLI tests, 15 text-attribute tests and 308 npm tests passed. Canonical x64/ARM64 build and npm/NuGet packaging passed; both NativeAOT binaries rendered SVG assets with verified dimensions and pixels. Generated schema/docs, plugin conformance, npm lint and format checks passed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Nikola Metulev (nmetulev)
left a comment
There was a problem hiding this comment.
🤖 AI-generated review (winappcli pr-review skill) — verify before acting.
Decision: changes required
Reviewed and stress-tested head 69b2c249c846c56bbc1144e5b3747c76bc21b0de, including inherited Native AOT support. Three issues reproduced; no fixes applied during testing.
Generic bundles ignore the evaluated packaging recipe
What is wrong: Single-project packaging forwards resolution.AppxRecipePath, but generic bundle slices do not. The fallback copies the build directory recursively rather than assembling the recipe's intended payload.
Show me: A copied dotnet sample emits a valid external recipe after publish that excludes a harmless plain-text test-only-not-a-key.pfx. Single x64 MSIX: sentinel absent, 249 entries. x64+ARM64 bundle: sentinel present at root and under publish/, 496 entries per slice. Both reproduced twice from clean outputs. An AOT control with explicit per-slice manifest architecture also includes managed leftovers and has no root executable at the manifest's dotnet-app.exe path; native executables instead sit under publish/ and native/.
Why it matters: Bundles can include unintended files and ship the wrong executable layout. This is synthetic sentinel evidence, not exposure of an actual private key.
Smallest fix: Forward the evaluated recipe in ProduceProjectSliceAsync, matching the single-package call, and cover both managed and AOT recipe-based bundles.
Location: PackageCommand.ProjectMode.cs:779-786 versus :506; recursive fallback in MsixService.cs:504-528.
Generic AOT packaging misses manifest architecture
What is wrong: When an external recipe is present, resolution retains build TargetDir, but architecture inference looks for the executable there before staging. The AOT executable is in publish output, so a manifest that omits ProcessorArchitecture stays without it.
Show me: Clean AOT sample single-package runs twice produce MSIX without Identity/@ProcessorArchitecture. Both bundle runs fail with Bundle slice 'dotnet-app_1.0.2.0.msix' has no Identity/@ProcessorArchitecture. No final bundle is produced. MakeAppx accepted single-package creation; installation of that package was not tested.
Why it matters: Project-mode AOT bundles fail despite a known target architecture and successful native publish.
Smallest fix: Use the resolved project architecture or the correctly staged executable for inference when the manifest omits architecture; preserve validation of explicit manifest values.
Location: ProjectRunService.cs:452-460, MsixService.cs:403-418,1126-1134, MsixService.ProjectBundle.cs:55-56.
AOT package misses the toolchain PATH handling already used by run
Show me: Real AI Dev Gallery, same environment with the Visual Studio Installer directory absent from PATH: AOT package fails with MSB3073/exit 123 because vswhere.exe cannot be resolved; run --aot publishes and launches successfully. Prepending the Installer directory makes package succeed.
Why it matters: A machine that can run the AOT workflow fails to package the same app without an extra environment workaround.
Smallest fix: Reuse the existing AOT publish environment setup for the packaging publish path rather than introducing another toolchain detector.
Location: existing BuildAotPublishEnvironment in ProjectRunService.Aot.cs:198-219; native project packaging publish invocation.
Real application coverage
AI Dev Gallery at 2406535b produced managed ARM64/x64 packages, a fully .NET-self-contained ARM64 package, an ARM64 AOT package, and an ARM64+x64 AOT bundle. Native-MSIX bundle slices had correct manifest/PE architectures and clean payloads. Managed -> AOT -> managed reuse showed no stale payload contamination.
The extracted ARM64 AOT package was registered under a unique current-user identity and exercised for 205 seconds: Samples, Filters, applied NPU filtering, and Settings. Loaded modules contained no CoreCLR/JIT or managed app DLL. This is packaged-payload loose-registration coverage, NOT signed-MSIX installation. x64 was cross-compiled, not executed.
Gallery includes the prior app-side FilterRecord AOT binding fix and required TreatWarningsAsErrors=false for dependency trimming/AOT warnings. NuGet mirror configuration was also needed. These are not vanilla-app success claims or comparable performance measurements. Gallery uses native MSIX tooling and did not hit the generic-path defects above.
Temporary registrations/output were removed, no certificates were created/trusted, and both worktrees remained clean. --name identity behavior and --self-contained naming concerns were checked and withdrawn as defects.
Stream redacted publish output directly instead of buffering JSON candidates. Add a real WinUI Native AOT sample regression covering recipe staging and the native entrypoint. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Verify the returned process runs the staged native executable, creates a responding window, and survives startup. Clean up the exact test process and unique registration. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Merge parent 0040047 including main be26cf7, direct redacted AOT streaming, updated sample regression, and the shipping XML boundary. Preserve all packaging work and stack metadata; no reviewed-defect fixes. Validation: 742 focused C# tests passed, one expected long-path skip; 308 npm tests passed. Canonical x64/ARM64 native build, npm/four NuGet packaging, generated surfaces, plugin checks, npm lint/format, sample-script parsing, and CLI/library XML packaging boundary passed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 1b7aaf4b-03f2-4a50-898f-74ff7160b51b
Merge validated parent c89706b including main 2e307c7 CsWin32 and analyzer dependency updates. Preserve remote packaging work and native stack metadata; sync only, no reviewed-defect fixes or history rewrite. Validation: 752 focused CLI tests passed, one expected long-path skip; analyzer suite 65/65; npm 308/308. Canonical x64/ARM64 native publish, Debug solution build, npm/four NuGet packages, generated schema/docs, plugin conformance and npm compile/lint/format passed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Narrow the best-effort cleanup catch blocks introduced by project-mode packaging to the file-system exceptions those Delete/Refresh operations actually throw (IOException, UnauthorizedAccessException, SecurityException), keeping the no-op best-effort semantics: - MsixService.NativePackaging.cs (staged artifact + temp manifest cleanup) - MsixService.ProjectBundle.cs (staged bundle + temp manifest cleanup) - PackageCommand.ProjectMode.cs (native + bundle scratch-dir cleanup) Narrow PathSafety.IsNetworkDriveRoot's two bias-to-unsafe catches to the documented exceptions Path.GetFullPath and DriveInfo throw, still returning true (refuse) for those cases. Use Path.Join instead of Path.Combine in CollectNativeFragmentDllNames so a non-rooted segment can't be misread as dropping earlier arguments. The command-boundary `catch (Exception ex)` handlers are intentionally left broad: they rethrow OperationCanceledException and convert every other failure into a friendly error and non-zero exit, matching the established command pattern. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dcabfb3c-9ea1-44c5-b91b-3b04f86a4289
Addresses three reproduced findings from PR review (generic bundle + native AOT packaging paths): 1. Generic bundle slices ignored the evaluated AppxPackageRecipe: only the single-package path forwarded resolution.AppxRecipePath, so bundle slices fell back to a recursive whole-folder copy and shipped unintended files / the wrong layout. Forward the recipe in ProduceProjectSliceAsync too. 2. Generic AOT packaging left the manifest without Identity/@ProcessorArchitecture when PE detection could not find the executable (the AOT exe is in publish/native output, not the recipe's build TargetDir), so architecture-only bundles failed validation. Add EnsureProcessorArchitecture to stamp the resolved target architecture as a fallback, preserving any explicit manifest value; call it from UpdateAppxManifestContentAsync. 3. Native AOT packaging did not reuse the AOT publish environment (which prepends the VS Installer directory so vswhere.exe resolves), so packaging an AOT app failed with MSB3073 on a machine whose PATH omits it, even though run --aot succeeds. Thread BuildAotPublishEnvironment() through the native publish via the argument-list overload of RunDotnetCommandAsync. Add tests: generic bundle slice recipe forwarding, EnsureProcessorArchitecture (stamp/preserve/no-op), and native publish using the env-capable overload. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dcabfb3c-9ea1-44c5-b91b-3b04f86a4289
#817 (winapp run --aot) squash-merged into main, so main's single squashed commit conflicts with the individual #817 commits this branch was stacked on. This branch already contains all of #817's work plus the pack project-mode feature, so every conflict resolves to this branch's version: the shared AOT files here are the project-mode superset (PublishAndResolve split, native-MSIX/signing evaluation, exact-RID handling, WinAppManifestPath and signing RequestedProperties, and the command-neutral non-executable error message that pack and run now share). Main carried only the older run-only versions of those same lines. The @types/node bump and other main-only changes auto-merged cleanly. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dcabfb3c-9ea1-44c5-b91b-3b04f86a4289
There was a problem hiding this comment.
🟡 Changes recommended
Critical path-safety issues and additional documented-behavior and native MSIX validation gaps remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
src/winapp-CLI/WinApp.Cli/Commands/PackageCommand.ProjectMode.cs:569
- When project signing evaluation returns
null, this branch treats the failure as an unsigned policy and proceeds. A project with signing enabled can therefore produce an unsigned MSIX if the preflight evaluation fails transiently or differs from the later publish evaluation; the method's contract says signing should not be silently downgraded. Return an actionable error forprops is null(for example, ask for--certor explicit--no-sign) and keep onlySigningEnabled == falseon the unsigned path.
var props = await projectRunService.EvaluateProjectSigningAsync(csproj, buildOptions, cancellationToken);
if (props is null || props.SigningEnabled == false)
{
// Could not evaluate, or signing explicitly disabled → deliver unsigned.
return (unsigned, null);
src/winapp-CLI/WinApp.Cli/Commands/PackageCommand.ProjectMode.cs:332
- The new native MSIX branch is only exercised with fakes: the handler tests inject a prebuilt
NativeMsixPublishOutcome, while the sample test covers the generic recipe path. No test runs a real WinUI/MSIX-tooling.csprojthroughdotnet publish,AppxPackageDir,AppxPackageOutputparsing, and final delivery/signing, so the primary documented workflow can regress while the suite remains green. Add one real Windows integration/sample check for this branch (or mark the feature blocked on that validation).
// MSIX-tooling projects (WinUI / EnableMsixTooling): let the Windows App SDK's own MSIX targets
// produce the package during publish, then sign and deliver it. The SDK owns file selection and
// Native AOT native/managed filtering, so winapp never repackages the output. A native project
// that fails to package is reported as-is — never a silent fall back to generic packaging.
if (await projectRunService.IsNativeMsixProjectAsync(csproj, buildOptions, cancellationToken))
src/winapp-CLI/WinApp.Cli/Commands/PackageCommand.cs:38
- The project-mode option set does not define or register
--runtime/-r, sowinapp package App.csproj --runtime win-arm64is rejected as an unknown option even though the PR description says project mode accepts the same build options aswinapp run, including--runtime/-r. Either implement the documented forwarding/architecture resolution or remove that promise from the user-facing description and generated API surfaces before shipping.
// Project-mode options (mirrors winapp run; inert unless the input is a .csproj).
public static Option<string> ConfigurationOption { get; }
public static Option<string[]> ArchOption { get; }
public static Option<string?> FrameworkOption { get; }
public static Option<bool> NoBuildOption { get; }
src/winapp-CLI/WinApp.Cli/Services/ProjectRunService.cs:614
- With
--no-build,RunProjectModeAsyncskipsIsDefinitivelyUnpackagedAsync, but this predicate still selects the native path solely fromMsixPackageSupport/EnableMsixTooling. Thuswinapp package App.csproj --no-build -p WindowsPackageType=Nonecan invoke forcedPublishAppxPackage=trueinstead of returning the documented unpackaged-project error. Treat an explicitWindowsPackageType=Noneas non-native here so the generic path performs the authoritative packaging check.
- Files reviewed: 37/37 changed files
- Comments generated: 3
- Review effort level: Lite (auto)
Note
Copilot is running an experiment and ran this review at Lite.
Resolve the generated npm command conflict from the merged CLI schema, preserving project packaging options and main's explicit empty-string forwarding and UI actions. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…MB probes Three Copilot review findings: - TryGetLoneRuntimeIdentifier returned the FIRST -p RuntimeIdentifier, but MSBuild uses the last assignment. `-p RuntimeIdentifier=win-x64 -p RuntimeIdentifier=win-arm64` silently derived x64 and built the wrong architecture. Return the last value to match MSBuild. - ResolveEvaluatedFileIfPresent (WinAppManifestPath / FinalAppxManifestName / AppxPackageRecipe) probed a project-evaluated path with File.Exists without the network/reparse guard used for PackageCertificateKeyFile, so a project with <WinAppManifestPath>\\host\share\...</WinAppManifestPath> could trigger an outbound SMB probe on `winapp package`. Treat a network / mapped-drive / reparse-redirected path as not-present without probing. - The native AppxPackageOutput guard's stated "not remote" invariant was enforced only lexically (IsNetworkPath + IsUnder), so a reparse point planted under the staging directory pointing at a share slipped through. Add CrossesReparsePoint so the guarantee actually holds. Add tests: last-RID-wins, and the network-path guard on the evaluated-file resolver. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dcabfb3c-9ea1-44c5-b91b-3b04f86a4289
main added deterministic explicit UI invoke actions (#846), which also changed the npm command generator (generate-commands.mjs now emits `!== undefined` guards). That regenerated winapp-commands.ts conflicted with this branch's project-mode packageApp options. Regenerated winapp-commands.ts, cli-schema.json, and npm-usage.md from the merged CLI so the wrapper combines main's generator style and new UI actions with the pack project-mode options. All other main changes auto-merged. Verified with tsc --noEmit. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: dcabfb3c-9ea1-44c5-b91b-3b04f86a4289
Integrate the author's last RuntimeIdentifier and evaluated-path checks without rewriting either validated main merge. Preserve regenerated npm bindings from the combined schema. Validation: 821 focused CLI tests passed with one expected skip; canonical x64/ARM64 CLI, npm and four NuGet packaging passed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Nikola Metulev (nmetulev)
left a comment
There was a problem hiding this comment.
🤖 AI-generated review (winappcli pr-review skill) — verify before acting.
Decision: changes required
Reviewed commit 394e2fa7ee00b97ab1b4e814daf84afbb14afe9e against main 09a401bc9877625a1e7b2b915fea09833bafbb05. The main merge conflict is resolved, but the five issues below were reproduced using the freshly published CLI. No review fixes were applied.
Must fix
Bundling can silently discard a requested architecture
- What is wrong: Packages with identical filenames overwrite each other in the bundle staging directory.
- Show me: On a copy of the WinUI sample, run:
The command succeeds, but
winapp pack .\winui-app.csproj --arch x64 --arch arm64 -p AppxPackageName=Fixed --no-sign
AppxMetadata/AppxBundleManifest.xmlcontains only an ARM64 application package namedFixed.msix. Without the filename override, the control bundle contains both x64 and ARM64. - Why it matters: A successfully generated distribution artifact silently excludes a requested architecture.
- Smallest fix: Give each staged package an architecture-qualified filename, as the existing folder-bundle path does, or reject duplicate filenames before copying.
- Location:
MsixService.ProjectBundle.cs:116;BundleService.cs:30-33.
Packaging can ship old files instead of the application just published
- What is wrong: Generic packaging obtains its output directory through a separate, non-publish evaluation. It can disagree with the actual publish operation.
- Show me: In an executable project with an authored manifest and no active native MSIX tooling, configure:
Then run
<PublishDir Condition="'$(_IsPublishing)' == 'true'">$(MSBuildProjectDirectory)\shipping\</PublishDir>
winapp pack .\App.csproj --arch arm64 --no-sign. On a clean project, publishing succeeds but packaging fails looking for the defaultbin\Release\...\publish\directory. After priming that default directory with an earlier publication, the command succeeds while packaging the old marker, even though the just-publishedshipping\directory contains the updated marker. Expected: package the files from this publication. - Why it matters: A successful command can deliver an older application.
- Smallest fix: Capture output properties from the actual publish invocation, following the existing AOT resolver's approach.
- Location:
ProjectRunService.cs:350-375,452-460.
A clean checkout can silently bypass project signing
- What is wrong: Signing policy is selected before restore and never reconsidered. Settings imported from a restored NuGet package are missing during that initial selection.
- Show me: A local test NuGet package imports the following build properties:
The sentinel contains deliberately invalid, harmless text, not a real certificate or key. On a generic packaged project with no restored
<PropertyGroup> <AppxPackageSigningEnabled>true</AppxPackageSigningEnabled> <PackageCertificateKeyFile>$(MSBuildProjectDirectory)\signing-sentinel.pfx</PackageCertificateKeyFile> </PropertyGroup>
objdirectory,winapp pack .\App.csproj --arch arm64 --output .\result.msixexits successfully and produces an unsigned MSIX with noAppxSignature.p7x. The identical second run correctly attempts signing and fails on the invalid certificate. Expected: the clean run must honor the restored signing requirement and fail too. - Why it matters: Clean CI builds can report success without enforcing the project's signing requirement.
- Smallest fix: Resolve authoritative signing settings after restore, before final delivery. Failed evaluation must not imply "unsigned" unless explicitly requested.
- Location:
PackageCommand.ProjectMode.cs:305-314,568-602.
Generic publishing can skip a restore it still needs
- What is wrong: A preliminary build-context restore causes winapp to inject
publish --no-restore, although publish-specific dependencies were not restored. - Show me: In a two-project solution without an explicit platform, give the generic packaged executable a publish-only dependency:
Use
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" Condition="'$(_IsPublishing)' == 'true'" />
JsonConvertin the application source.winapp pack .\App.csproj --arch arm64 --no-signfails compilation because Newtonsoft is missing. The captured invocation shows winapp adding--no-restore. Ordinarydotnet publish .\App.csproj -c Release -r win-arm64restores the dependency and succeeds. - Why it matters: Valid publishing configurations fail despite the user never requesting
--no-restore. - Smallest fix: Preserve the user's restore preference for publication rather than reusing the build-only "already restored" optimization.
- Location:
ProjectRunService.cs:304-306,338-341.
Generic NativeAOT packaging still misses toolchain PATH preparation
- What is wrong: The generic publisher does not supply the environment preparation already used by
run --aotand native-MSIX packaging. - Show me: With NativeAOT prerequisites installed but the Visual Studio Installer directory absent from
PATH, package a generic project configured with<PublishAot>true</PublishAot>.winapp pack .\App.csproj --arch arm64 --no-signfails withvswhere.exenot recognized / MSB3073, exit 123. The identical command succeeds when onlyC:\Program Files (x86)\Microsoft Visual Studio\Installeris prepended to the child process'sPATH. - Why it matters: AOT packaging unnecessarily depends on the caller using a specially configured shell. The already-corrected native-MSIX route does not fix this separate generic route.
- Smallest fix: Use the existing environment-capable dotnet invocation with
BuildAotPublishEnvironment, retaining streamed diagnostics. - Location:
ProjectRunService.cs:974-1015.
What was exercised
- Final merged code: 821 focused C# tests passed, one expected long-path skip; 311 npm tests passed.
- Canonical x64/ARM64 NativeAOT build, npm/four NuGet packages, generated schema/docs, plugin validation, and npm checks passed.
- Reproduced all five findings with the published ARM64 CLI, version
0.6.3-zt-776-pack-project-mode.156, SHA256A8F0CAF48459557B16614CB4448D1F3B5988A38E9C348B961E616A6AA854F903, built from the reviewed source tree. - Used a real WinUI sample copy for the bundle case and isolated generic managed/AOT projects for the others, with control runs and archive inspection.
- No application installation, certificate trust changes, or UI launches. The signing test used harmless text, not a real key. Temporary projects were cleaned up.
…ct mode Addresses review findings on winapp pack project mode: - Publish evaluation now runs in the publish context (-p:_IsPublishing=true), so a project that conditions PublishDir on _IsPublishing is packaged from the directory publish actually wrote, not a stale/default one. - The publish pass no longer reuses the build-context 'already restored' optimization, so publish-only (_IsPublishing) dependencies are restored. - Signing is no longer silently downgraded to unsigned when the project's signing configuration cannot be evaluated: the signing probe restores once and retries (so package-imported signing props are visible on a clean checkout), and an undeterminable configuration is now an actionable error unless the user passed --no-sign/--cert/--generate-cert. - The generic publish pass applies the Native AOT publish environment (VS Installer on PATH) so a generic <PublishAot> project packages without a specially configured shell, matching native-MSIX packaging and run --aot. - Bundle staging disambiguates duplicate slice file names so no architecture is silently dropped from a bundle. - Project build-output directories (TargetDir/PublishDir) resolving to a network location are rejected before any filesystem probe, matching the keyfile guard. - Project bundles run the shared BundleValidationService (identity, capabilities, dependencies, target device families, applications, architectures) instead of a narrower subset, and slice manifest lookup is case-insensitive. - A known exact-RID ProjectRunException from pre-build probes is surfaced as an actionable error instead of the generic 'unexpected error' handler. - A -p Platform that conflicts with --arch (or any architecture-specific Platform in a multi-arch bundle) is rejected rather than mis-packaged. - Docs note that --manifest/--executable/--skip-pri are rejected for MSIX-tooling project mode. Adds project-mode regression tests for the signing, network-path, and -p Platform behaviors. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…aluate The first attempt restored and re-evaluated signing only when the initial --getProperty evaluate returned null. But on a clean checkout that evaluate SUCCEEDS (exit 0) and returns NuGet-imported signing properties empty rather than failing, so the retry never fired and a project that requires signing could still be silently delivered unsigned. Key the restore-and-retry on whether the project has actually been restored (its evaluated ProjectAssetsFile exists) instead of on a null result, so the clean- checkout case is covered. Honors --no-restore. Adds real-service regression tests for the unrestored, restored, and --no-restore signing paths. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ne' may silently drop its earlier arguments' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
| var directory = finalPath.Directory!.FullName; | ||
| var stem = Path.GetFileNameWithoutExtension(finalPath.Name); | ||
| var extension = finalPath.Extension; // includes the leading dot, or empty when there is none | ||
| return new FileInfo(Path.Combine(directory, $"{stem}.winapp-{Guid.NewGuid():N}{extension}")); |
Description
Adds project-mode support to
winapp package(issue #776). You can now pass an explicit.csprojdirectly:winapp package MyApp.csprojbuilds the project and packages its output in one step — no need to build separately or hunt for the correct output folder. This is the packaging counterpart to #633, which added project mode towinapp run.In project mode the command reuses
winapp run's build/evaluate infrastructure (IProjectRunService): it builds the project, resolves the build output (TargetDir), and feeds it into the existing MSIX pipeline. Existing folder, multi-folder bundle, and sparse-manifest inputs are unchanged — only a single explicit.csprojtriggers project mode.The reuse seam required extending
MsixService.CreateMsixPackageAsyncso it is project- and architecture-aware:ResolveDotNetPackageListAsync) instead of probing the current directory, so packaging a nested project from a solution root resolves the right dependencies.targetArchinto runtime staging and activation-manifest embedding, so--arch arm64stages arm64 runtime bits rather than the host's.All new parameters are additive; folder/bundle callers pass
nulland behave exactly as before. Owning-solution context is threaded into the build so projects that reference$(SolutionDir)build the same as underwinapp run.Usage Example
Project mode accepts the same build options as
winapp run:-c/--configuration,--arch,-r/--runtime,-f/--framework,--no-build,--no-restore, and repeatable-p/--property. A project that builds as an unpackaged app (WindowsPackageType=None) has no MSIX manifest to package and produces an actionable error.Related Issue
Closes #776
Type of Change
Checklist
PackageCommandProjectModeTests)plugins/winapp/skills/(if CLI commands/workflows changed)Additional Notes
.csprojis project mode; solutions and directory-to-project resolution are intentionally out of scope, so existing directory inputs stay unambiguous.TryResolveArchitecturewas promoted toRunArchHelperand is now shared byrunandpackage; the non-executable-project error message was made command-neutral.docs/cli-schema.jsonand the npm typed SDK (winapp-commands.ts); updateddocs/usage.md,docs/guides/dotnet.md, the package skill, and the Copilot agent reference..csproj(and cross-arch self-contained staging) was not exercised in CI-less environments that lack the internal NuGet feed; unit + mock coverage is comprehensive, but a reviewer with the feed should confirm the real build->package path on at least one packaged WinUI app.--runtime/-ronpackageonly restates--arch(the RID reduces to the same architecture). It's kept for symmetry withwinapp run/dotnet build -r; flagging in case reviewers prefer to drop it before it ships.