Add .csproj "project mode" to winapp run#612
Conversation
Build Metrics ReportBinary Sizes
Test Results✅ 3803 passed, 5 skipped out of 3808 tests in 580.4s (+222 tests, -41.4s vs. baseline) Test Coverage✅ 94.4% line coverage, 88.4% branch coverage · CLI Startup Time49ms median (x64, Updated 2026-07-18 08:34:49 UTC · commit |
Addresses PR #612 review findings on the highest-risk runtime + launch paths. H1 (arch-correctness): the reused Windows App Runtime install is now truly arch-correct with a real presence gate. WorkspaceSetupService.InstallWindows- AppRuntimeAsync separates the loose-layout/inventory arch (resolved target arch, used for the win10-<arch> path and ParseMsixInventoryAsync) from the nullable skip-filter arch, and a new IsWindowsAppRuntimeRegistered(arch) checks for BOTH a Framework package (Microsoft.WindowsAppRuntime.*, excluding .CBS.) AND a DDLM (Microsoft.WinAppRuntime.DDLM.*), arch-filtered — mirroring WinAppSDK's IsRuntimeRegisteredForCurrentUser. EnsureWindowsAppRuntimeInstalledAsync now throws if the target-arch runtime is still absent after install instead of treating a missing runtime dir as success, so a cross-arch run no longer skips the install and crashes at bootstrap. L2 (folder mode unchanged): the process-arch handling is scoped so folder mode passes a null arch filter — byte-for-byte identical to the pre-feature behavior. M3 (PID-reuse / exit-code race): LaunchExecutable now returns an owned ILaunchedProcess wrapper (new ILaunchedProcess.cs) instead of a bare pid, so the unpackaged wait/detach/debug flow holds the real Process handle and reads its exit code directly rather than re-attaching via Process.GetProcessById (which could hit a reused pid or an already-exited process wrongly reporting 0). Adds WorkspaceSetupServiceRuntimeGateTests and updates the fakes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…2, M4, M5) Addresses PR #612 review findings on project-mode property resolution. H2 (--json unparseable): stdout in --json mode is now pure JSON. The human- readable "Building..." banner is gated behind !Json and dotnet build diagnostics on a failed build are routed to stderr, so a --json consumer's stdout parses. Adds banner-suppression tests (json vs non-json). M1 (arg escaping): BuildDotnetArguments uses WindowsCommandLine.JoinArguments instead of a hand-rolled QuoteIfNeeded that mishandled trailing backslashes. M2 (--no-build precedence): user -p properties are emitted before the dedicated -c/--arch/-f flags on the --no-build path too, so the dedicated flag wins last (matching the build path and WarnOnOverriddenFlags). Adds a precedence test. M4 (getProperty parse robustness): MsBuildPropertyReader scans each '{' and uses Utf8JsonReader/TryParseValue, tolerating leading diagnostics/preamble and trailing content, and only accepts a Properties object — so a single-property scalar that merely contains '{' is no longer misread as JSON. Adds parser tests. M5 (multi-project disambiguation): adds coverage for a test project (IsTest- Project=true) being excluded from the executable set and for the no-executable-among-multiple ambiguity error. The evaluated OutputType remains the source of truth downstream (BuildAndResolveAsync rejects non-runnable OutputType), with the static parse only used to disambiguate at input time. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Addresses PR #612 review findings. M8: regenerates src/winapp-npm/src/winapp-commands.ts from the current CLI schema so it includes the run project-mode options (--arch, --no-build, --no-restore, -r/--runtime, -c/--configuration, -f/--framework, -p). The generate-commands --check gate now passes. H3: samples/winui-app/test.Tests.ps1 registered a loose-layout package then deleted its backing files without unregistering, polluting the machine/CI. The AfterAll now reads the identity Name from Package.appxmanifest and removes the package (Get-AppxPackage | Remove-AppxPackage) before deleting temp/bin/obj. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
specs/winapp-run-csproj.md's header linked an internal/personal planning repo (`nikolame/win-devex`) that isn't accessible to public readers of microsoft/winappCli and whose "overall plan" does not live in this repo. Ahead of #612 going public, drop that Related line (the same public-appropriateness rule #616 passed clean). Verified single occurrence: `git grep win-devex` and `git grep nikolame/` now return nothing on tracked files. Docs-only; no code, schema, generated-skill, or npm change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: b05ed756-966d-4260-a593-f507c4cb2446
Adds a "project mode" to `winapp run` so it can target a .NET `.csproj` (or a directory containing one) directly, in addition to the existing build-output folder mode. In project mode winapp restores, builds, evaluates the project's MSBuild output properties, provisions the Windows App Runtime for the resolved architecture, and launches the app. New `run` options: --arch, --configuration, --framework, --no-build, --no-restore, --property, --runtime. Folder mode is unchanged. Rebased onto main: reconciled the feature's enriched service contracts (IWorkspaceSetupService.InstallWindowsAppRuntimeAsync architecture arg + runtime-package result, IsWindowsAppRuntimeRegistered; IPackageRegistrationService.GetInstalledVersion architecture arg, IsPackageInstalled) with main-authored fakes and tests, and regenerated derived artifacts (docs/cli-schema.json, winapp-cli skills, .claude mirror, winapp-commands.ts). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
af418e1 to
65100e7
Compare
Empty commit to fire a pull_request synchronize event now that the PR base is main (the rebase force-push happened while the base was still winui-devex, so no workflows matched their branches: [main] filter). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
Project mode now accepts a solution (.sln/.slnx) or a directory containing one, in addition to a .csproj. When given a solution, `winapp run` resolves the runnable app project from `dotnet sln list`, classifying each candidate by evaluated OutputType/IsTestProject, then builds that project with the solution's $(SolutionDir) and sibling Solution* MSBuild properties defined. This makes projects that depend on $(SolutionDir) build exactly as they do under `dotnet build <sln>` / Visual Studio, closing the AI-Dev-Gallery-on-x64 class of failure where a bare .csproj build leaves $(SolutionDir) undefined. A directory containing a solution prefers the solution over loose .csproj files. When a solution or directory has multiple runnable app projects, `run` no longer guesses a startup project: the new `--project <name-or-path>` selector chooses one, otherwise it errors listing the candidates. - ProjectRunService: solution/directory resolution + Solution* property injection into the build and evaluate passes; `dotnet sln list` parsing. - RunCommand: new --project option; broadened input description. - Regenerated cli-schema.json, skills, and npm winapp-commands.ts. - Tests for solution resolution, ambiguity, --project selection, and Solution* property emission. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
Addresses CodeQL 'Path.Combine may silently drop earlier arguments' on two non-rooted segment concatenations (win10-<arch> layout dir; per-test temp dir). Path.Join concatenates without the rooted-reset behavior; result is identical for these never-rooted segments but no longer matches the flagged pattern. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
Fixes from the PR #612 review (H1, M3, M6, M7, M8, M10, L1, L4, NOTE): - M3: kill the dotnet/MSBuild process tree on Ctrl+C in RunDotnetStreamingAsync so builds are not orphaned. - M8: gate the build banner + streamed passthrough on log level so --quiet keeps stdout clean (route build output to stderr), matching --json behavior. - M6/M7/L4: solution-prop tokens in classification eval, resolve --project selectors against the input dir, run SDK check before `dotnet sln list`. - NOTE: skip Windows App Runtime preparation when the resolved project has no Microsoft.WindowsAppSDK reference (plain console/desktop Exe), avoiding the noisy "could not determine runtime" warning. - M10: npm run() emits app args after `--` separator (generator fix). - H1/L1: refresh hand-maintained agent docs + run help text for csproj/sln inputs, unpackaged behavior, and app-args-after-`--`. Tests: add MatchProjectSelector (--project resolution) and ReferencesWindowsAppSdk (runtime-skip gate) coverage; add a --quiet banner-suppression test. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
L2 (RunArchHelper): move architecture-detection logic fully into RunArchHelper.DefaultArchitecture(); WorkspaceSetupService.GetSystemArchitecture() is now a thin delegator instead of the source of truth. M9 (extract Windows App Runtime install): introduce IWindowsAppRuntimeService / WindowsAppRuntimeService and move all runtime install / gate / locate / inventory members out of the oversized WorkspaceSetupService. Rewire production callers (WorkspaceSetupService, MsixService + partials, UpdateCommand) and DI. This removes the runtime-related growth this PR introduced; residual WorkspaceSetupService size is the pre-existing SetupWorkspaceAsync orchestration (out of scope). M5 (reuse ProjectDetectionService): move the evaluated executable/non-test project classification into ProjectDetectionService (IsExecutableNonTestProjectAsync) with a shared IsExecutableOutputType helper used by both the static and evaluated paths. ProjectRunService now depends on IProjectDetectionService and no longer carries a parallel classifier. Pure refactor: no CLI surface/description changes; npm commands and .claude mirror verified drift-free. Targeted suites pass (detection/project-run 122, runtime/ workspace/msix/update/arch 368). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
C#/WinRT authoring projects (Microsoft.Windows.CsWinRT consumers, WinUI control libraries that author winmds) fail to build via `winapp run` project mode on hosts with no registered Windows SDK: CsWinRT.targets defaults CsWinRTWindowsMetadata to a bare SDK version that cswinrt.exe resolves through a registry KitsRoot10 lookup, which fails and cascades into WMC XAML errors. Add a temporary CsWinRTMetadataShimService that, only when no Windows SDK is registered, resolves the Microsoft.Windows.SDK.NET.Ref ref-pack winmd folder from the NuGet cache (preferring the TargetPlatformVersion match, else highest, verifying FoundationContract.winmd) and injects it as -p:CsWinRTWindowsMetadata into both the build and evaluate passes. It is skipped when an SDK is installed or the user set the property, and no-ops cleanly (debug log) when the ref pack is unavailable so the normal error still surfaces. Marked as a shim pending the upstream cswinrt targets fix. Adds unit tests for SDK-absent detection and winmd-folder resolution, and threads the property through ProjectRunService's build/evaluate builders. Updates specs/winapp-run-csproj.md (new 8.6) and docs/guides/dotnet.md. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
Project-mode `winapp run` rejected identity-only options (--no-launch, --with-alias, --unregister-on-exit, --clean, --manifest, --output-appx-directory) only in the authoritative post-build gate, so a user paid the full build cost before the incompatible argument combination was rejected. Add a cheap, evaluate-only WindowsPackageType probe (IProjectRunService.IsDefinitivelyUnpackagedAsync) and a pre-build fast-fail in RunProjectModeAsync: when an incompatible option is present and the project is DEFINITIVELY unpackaged (explicit WindowsPackageType=None), reject before building. Packaged/indeterminate projects (empty WindowsPackageType, or a failed evaluation) fall through to the build + the authoritative gate, which still catches the indeterminate-then-unpackaged case that only resolves post-build (e.g. identity via an emitted recipe). Skipped under --no-build (no build cost to save). Extract the incompatible-option collection + message into shared helpers so the fast-fail and the post-build gate stay in lockstep. Tests: pre-build fast-fail on definitively-unpackaged; probe skipped for compatible options and under --no-build; indeterminate builds then rejects at the gate; service-level probe (None -> true, MSIX/empty -> false, failed evaluate -> false). Spec §7.3 updated. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
There was a problem hiding this comment.
Pull request overview
Adds .csproj/solution project mode to winapp run, supporting build-and-launch workflows for packaged and unpackaged WinUI applications.
Changes:
- Adds project detection, MSBuild evaluation, architecture handling, and direct executable launch.
- Extracts architecture-aware Windows App Runtime installation.
- Adds SDK wrappers, documentation, samples, CI coverage, and extensive tests.
Reviewed changes
Copilot reviewed 74 out of 74 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/winapp-npm/src/winapp-commands.ts |
Updates generated run SDK API. |
src/winapp-npm/scripts/generate-commands.mjs |
Generates repeatable options and run passthrough. |
src/winapp-CLI/WinApp.Cli/Services/WorkspaceSetupService.cs |
Delegates runtime installation. |
src/winapp-CLI/WinApp.Cli/Services/ProjectDetectionService.cs |
Adds evaluated project classification. |
src/winapp-CLI/WinApp.Cli/Services/PackageRegistrationService.cs |
Adds architecture-aware package lookup. |
src/winapp-CLI/WinApp.Cli/Services/MsixService.Runtime.cs |
Uses extracted runtime service. |
src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs |
Adds project-aware runtime preparation. |
src/winapp-CLI/WinApp.Cli/Services/MsixService.cs |
Injects runtime service. |
src/winapp-CLI/WinApp.Cli/Services/IWorkspaceSetupService.cs |
Removes runtime responsibilities. |
src/winapp-CLI/WinApp.Cli/Services/IWindowsAppRuntimeService.cs |
Defines runtime installation contract. |
src/winapp-CLI/WinApp.Cli/Services/IProjectRunService.cs |
Defines project-mode workflow. |
src/winapp-CLI/WinApp.Cli/Services/IProjectDetectionService.cs |
Exposes evaluated classification. |
src/winapp-CLI/WinApp.Cli/Services/IPackageRegistrationService.cs |
Adds architecture filters. |
src/winapp-CLI/WinApp.Cli/Services/IMsixService.cs |
Exposes runtime preparation inputs. |
src/winapp-CLI/WinApp.Cli/Services/ILaunchedProcess.cs |
Abstracts owned child processes. |
src/winapp-CLI/WinApp.Cli/Services/IDotNetService.cs |
Adds streaming dotnet execution. |
src/winapp-CLI/WinApp.Cli/Services/ICsWinRTMetadataShimService.cs |
Defines SDK-less metadata shim. |
src/winapp-CLI/WinApp.Cli/Services/IAppLauncherService.cs |
Adds direct executable launch. |
src/winapp-CLI/WinApp.Cli/Services/DotNetService.cs |
Implements streamed builds. |
src/winapp-CLI/WinApp.Cli/Services/CsWinRTMetadataShimService.cs |
Resolves NuGet WinMD metadata. |
src/winapp-CLI/WinApp.Cli/Services/AppLauncherService.cs |
Launches unpackaged executables. |
src/winapp-CLI/WinApp.Cli/Models/ProjectRunModels.cs |
Adds project-mode models. |
src/winapp-CLI/WinApp.Cli/Helpers/RunArchHelper.cs |
Maps architectures and RIDs. |
src/winapp-CLI/WinApp.Cli/Helpers/MsBuildPropertyReader.cs |
Parses MSBuild property output. |
src/winapp-CLI/WinApp.Cli/Helpers/HostBuilderExtensions.cs |
Registers new services. |
src/winapp-CLI/WinApp.Cli/Commands/UpdateCommand.cs |
Uses runtime service. |
src/winapp-CLI/WinApp.Cli/Commands/RunCommand.ProjectMode.cs |
Implements project-mode routing and launch. |
src/winapp-CLI/WinApp.Cli/Commands/RunCommand.cs |
Adds project inputs and options. |
src/winapp-CLI/WinApp.Cli.Tests/WorkspaceSetupServiceRuntimeGateTests.cs |
Tests runtime presence gating. |
src/winapp-CLI/WinApp.Cli.Tests/WorkspaceSetupServiceMsixTests.cs |
Updates runtime MSIX tests. |
src/winapp-CLI/WinApp.Cli.Tests/WorkspaceSetupServiceInventoryTests.cs |
Tests target-architecture inventory selection. |
src/winapp-CLI/WinApp.Cli.Tests/UpdateCommandTests.cs |
Updates runtime service fakes. |
src/winapp-CLI/WinApp.Cli.Tests/RunArchHelperTests.cs |
Tests architecture mappings. |
src/winapp-CLI/WinApp.Cli.Tests/ProjectDetectionServiceTests.cs |
Supplies dotnet dependency. |
src/winapp-CLI/WinApp.Cli.Tests/ProjectDetectionServiceAccessDeniedTests.cs |
Updates service construction. |
src/winapp-CLI/WinApp.Cli.Tests/PackageRegistrationServiceTests.cs |
Tests architecture mapping. |
src/winapp-CLI/WinApp.Cli.Tests/MsixServiceIdentityTests.cs |
Tests runtime identity preparation. |
src/winapp-CLI/WinApp.Cli.Tests/MsBuildPropertyReaderTests.cs |
Tests MSBuild output parsing. |
src/winapp-CLI/WinApp.Cli.Tests/LevelLogger.cs |
Adds configurable test logger. |
src/winapp-CLI/WinApp.Cli.Tests/FakeWorkspaceSetupService.cs |
Removes runtime behavior. |
src/winapp-CLI/WinApp.Cli.Tests/FakeWindowsAppRuntimeService.cs |
Adds runtime service fake. |
src/winapp-CLI/WinApp.Cli.Tests/FakeProjectRunService.cs |
Adds project-run fake. |
src/winapp-CLI/WinApp.Cli.Tests/FakePackageRegistrationService.cs |
Supports architecture-aware tests. |
src/winapp-CLI/WinApp.Cli.Tests/FakeMsixService.cs |
Records runtime preparation calls. |
src/winapp-CLI/WinApp.Cli.Tests/FakeDotNetService.cs |
Simulates streamed builds and evaluation. |
src/winapp-CLI/WinApp.Cli.Tests/FakeCsWinRTMetadataShimService.cs |
Adds metadata shim fake. |
src/winapp-CLI/WinApp.Cli.Tests/FakeAppLauncherService.cs |
Simulates direct process launch. |
src/winapp-CLI/WinApp.Cli.Tests/CsWinRTMetadataShimServiceTests.cs |
Tests SDK-less metadata resolution. |
samples/winui-unpackaged-app/winui-unpackaged-app.csproj |
Defines unpackaged WinUI sample. |
samples/winui-unpackaged-app/test.Tests.ps1 |
Tests unpackaged project mode. |
samples/winui-unpackaged-app/README.md |
Documents the sample. |
samples/winui-unpackaged-app/MainWindow.xaml.cs |
Implements sample interaction. |
samples/winui-unpackaged-app/MainWindow.xaml |
Defines sample window. |
samples/winui-unpackaged-app/App.xaml.cs |
Starts the sample window. |
samples/winui-unpackaged-app/App.xaml |
Defines sample resources. |
samples/winui-unpackaged-app/app.manifest |
Configures unpackaged process behavior. |
samples/winui-unpackaged-app/.gitignore |
Ignores build artifacts. |
samples/winui-app/test.Tests.ps1 |
Tests packaged project mode. |
README.md |
Lists WinUI samples. |
docs/usage.md |
Documents project-mode CLI usage. |
docs/guides/dotnet.md |
Adds .NET project-mode guidance. |
docs/fragments/skills/winapp-cli/setup.md |
Updates generated skill source. |
docs/cli-schema.json |
Regenerates run command schema. |
.github/workflows/test-samples.yml |
Adds WinUI sample jobs. |
.github/plugin/skills/winapp-cli/setup/SKILL.md |
Regenerates plugin guidance. |
.github/plugin/agents/winapp.agent.md |
Updates agent command guidance. |
.claude/skills/winapp-setup/SKILL.md |
Synchronizes Claude skill. |
.claude/agents/winapp.md |
Synchronizes Claude agent. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
C1 (CsWinRT shim restore ordering): on a genuinely clean SDK-less host the Microsoft.Windows.SDK.NET.Ref ref pack may not be on disk when the shim first resolves, so the very first `dotnet build` fails even though it restores the ref pack. Add IsWindowsSdkAbsent() to the shim and run an explicit `dotnet restore` before re-resolving, then build with --no-restore to avoid a double restore. Only fires when the SDK is absent, the user didn't set the property, and restore isn't disabled. Adds 4 tests + a BuildRestorePassArguments unit test. C2 (TFM-aware runtime resolution): a multi-targeted project could gate the Windows App Runtime on a sibling TFM's Windows App SDK version. Thread the effective --framework through EnsureWindowsAppRuntimeInstalledAsync and narrow the package list to the built TFM (FilterPackageListToFramework) before resolving the SDK version. Fail-open when the TFM is null or absent. Adds ProjectRunResolution.Framework and 3 filter tests. C6 (npm generator back-compat): passthrough run/tool/store wrappers dropped the bare-string appArgs form. Accept string | string[] and normalize before pushing after `--`. Regenerated winapp-commands.ts (--check clean). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
CI's build-and-package promotes analyzer warnings to errors. Two pre-existing violations in ProjectRunServiceTests.cs blocked the build: - CA1859: NewDetection helper returned the interface IProjectDetectionService instead of the concrete ProjectDetectionService. - CA1861: an inline constant array argument to CollectionAssert.AreEqual; replaced with direct count + element assertions. Verified with `dotnet build winapp.sln -c Release -warnaserror` (0 warnings). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
winapp run's solution and multi-.csproj resolution now auto-selects the real app when a solution also contains its test project (the AI Dev Gallery / WinUI Gallery shape), instead of erroring on ambiguity. A WinUI MSTest project is itself a packaged WinExe app that never sets IsTestProject, so OutputType alone cannot distinguish it from the real app. A candidate is now classified as a test project when it sets IsTestProject, has the TestContainer project capability, or references a known test framework/host package (Microsoft.NET.Test.Sdk, Microsoft.TestPlatform.TestHost, MSTest.*, xunit*, nunit*). Resolution: one non-test app -> run it; several apps -> require --project; zero apps but exactly one runnable test project (tests-only solution) -> run it with a note; several test-only -> require --project. An explicit --project/.csproj selector is always honored, even to select a test project. Classification is centralized in IProjectDetectionService.ClassifyRunnableAsync (replacing IsExecutableNonTestProjectAsync), which prefers an MSBuild evaluation (OutputType/IsTestProject + ProjectCapability/PackageReference items) and falls back to a static XML parse when evaluation is unavailable. Adds MsBuildPropertyReader.ParseItems for the --getItem envelope. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 74 out of 74 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs:155
- The packaged project path drops the selected
--frameworkhere.GetPackageListAsyncreturns every TFM, and runtime resolution takes the first WindowsAppSDK reference, so a multi-targeted packaged project built with framework B can install/register against framework A's runtime. Threadresolution.FrameworkthroughExecuteRunPipelineAsync/AddLooseLayoutIdentityAsyncand pass it toResolveDotNetPackageListAsync, as the unpackaged path already does.
- MatchProjectSelector: skip the leaf-name fallback for a fully qualified selector so `--project C:\wrong\App.csproj` no longer silently matches a same-named project elsewhere in the solution. Add unit tests covering the relative-leaf match, fully-qualified wrong path (no match), and fully-qualified correct path. - ProjectDetectionService.IsKnownTestPackage: collapse foreach to Any(). - MsBuildPropertyReader.TryReadItemsObject: filter non-array groups with Where(). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 19da51c1-361e-4b9b-939e-f9c51d4123c5
Summary
Implements
.csproj"project mode" forwinapp runper the committed specspecs/winapp-run-csproj.md(v0.4).winapp run <MyApp.csproj>— orwinapp run ./ a folder containing a single executable.csproj— now builds the WinUI project and launches it in one step, supporting both packaged and unpackaged WinUI apps. Existing folder mode is unchanged (all new options are optional; project mode is a new branch keyed on the input pointing at / containing a top-level buildable.csproj).What's implemented (per spec §12 phases)
DirectoryInfo→FileSystemInfo;ResolveInputclassifies Folder vs Project; multi-.csprojambiguity error (prefers the evaluatedOutputTypeover the static XML parse inProjectDetectionService).dotnet build <csproj> -t:Build --getProperty:...(with the required-t:Build);MsBuildPropertyReaderhandles both the JSON (multi-prop) and raw scalar (single-prop) shapes; repeatable-p/--propertyforwarded to both build and evaluation; dedicated-flag-beats--pprecedence mirrored.WindowsPackageType(None⇒ unpackaged); launches the apphostRunCommandexe directly via newAppLauncherService.LaunchExecutable; reuses the existing WinAppSDK runtime install (EnsureWindowsAppRuntimeInstalledAsync), gated onWindowsAppSDKSelfContained.TargetDir+ AUMID launch (reuses the folder-mode pipeline); guardrail error when a project resolves to packaged but has noPackage.appxmanifest.WorkspaceSetupService.InstallWindowsAppRuntimeAsyncparameterized by the app's resolved--arch(was CLI-process arch only), with a Framework+DDLM presence check (GetInstalledVersion(name, architecture)) to avoid needless reinstalls.Scope guards: .NET-only; no
--packaged/--unpackagedflags (packaging is detected from the evaluatedWindowsPackageType). A--projectselector is provided for solution/multi-project inputs (per spec v0.4). Unpackaged runs reject identity-only options (--manifest,--no-launch,--with-alias,--clean,--unregister-on-exit,--output-appx-directory) — fast-failing before the build when the project is definitively unpackaged, and via an authoritative post-build gate otherwise.--getPropertyavailability probed via runtime capability (SDK 8.0.100+), erroring clearly otherwise.New CLI options (project mode only; ignored in folder mode)
-c/--configuration(defaultDebug),--arch,-r/--runtime,-f/--framework,--project(select the app to launch from a.sln/.slnxor a multi-project directory),--no-build,--no-restore,-p/--property(repeatable).Test coverage
C# unit/routing (144 pass, Release):
MsBuildPropertyReaderTests— JSON/scalar/preamble/empty/case/missing-prop parsing.RunArchHelperTests— arch normalization, RID/Platform mapping.ProjectRunServiceTests—BuildDotnetArguments(default/arm64/--no-build/user--p:Platformsuppression/framework), SDK-version parsing,ResolveInputincl. multi-csproj ambiguity.RunCommandProjectModeTests(13) — unpackaged launch + runtime-arch, arm64, self-contained skip, identity-option rejection, forced-unpackaged property forwarding, packaged AUMID + arch, packaged-no-manifest guardrail, SDK-too-old, build-fail exit-code propagation, invalid-arch, resolve ambiguity, folder-mode regression.RunCommandTests— project-mode option parsing +TryResolveArchitecture; existing folder-mode tests unchanged.Pester sample & guide (spec §10):
samples/winui-unpackaged-app: new minimal unpackaged WinUI app; test builds +winapp run .and asserts the app boots off the reused runtime install (verified locally: app process stays alive). Also asserts evaluatedWindowsPackageType == None.samples/winui-app(packaged): project-modewinapp run . --no-launchfrom a clean copy + evaluatedWindowsPackageType != None..github/workflows/test-samples.ymlmatrix.Docs / samples updated
docs/usage.md(run section: folder-vs-project auto-detection + project-mode subsection),docs/guides/dotnet.md,docs/fragments/skills/winapp-cli/setup.md(hand-written template; generatedSKILL.md/cli-schema.json/.clauderegenerated viagenerate-llm-docs.ps1), newsamples/winui-unpackaged-app.Deferred / notes
dotnet runhook stays folder-based — unchanged, as decided.build-cli.ps1were not run locally: the AOT publish needs the MSVC linker env (missing in this sandbox) and npmbin/blobs are gitignored/CI-produced. The CLI + tests build clean in Release (0 warnings / 0 errors, warnings-as-errors on), andcli-schema.json/ skills were regenerated with the correct0.4.1version. CI's Build & Package workflow will produce the AOT artifacts.Co-authored-by: Copilot App 223556219+Copilot@users.noreply.github.com