Preserve MSIX app data (LocalState) across winapp run re-deploys#403
Preserve MSIX app data (LocalState) across winapp run re-deploys#403
Conversation
Previously, winapp run called RemovePackageAsync without RemovalOptions, which wiped the package's LocalState, RoamingState, and Settings folders on every re-deploy. Apps writing to ApplicationData.Current.LocalFolder would lose all persisted data between runs. Now UnregisterAsync passes RemovalOptions.PreserveApplicationData by default when removing dev-mode packages before re-registration. This preserves application data across winapp run invocations. Added --clean flag to winapp run for when a fresh start is needed (e.g., reset corrupted state or test first-run behavior). Explicit unregister (winapp unregister, --unregister-on-exit) still removes application data, since those are intentional cleanup actions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Updates winapp run re-deploy behavior so MSIX app data (e.g., LocalState / settings) is preserved by default, with an explicit --clean flag to opt into wiping state when needed. This reduces the “data disappears between runs” pain point while keeping explicit cleanup commands destructive.
Changes:
- Add
preserveAppDatasupport to package removal and default it to preserving application data for run re-deploy scenarios. - Introduce
--cleanonwinapp run(and plumb it through CLI + npm wrapper) to force a fresh state. - Update docs/schema and test fakes to reflect the new flag/signatures.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/winapp-npm/src/winapp-commands.ts | Adds clean option and forwards --clean to the CLI. |
| src/winapp-CLI/WinApp.Cli/Services/PackageRegistrationService.cs | Implements RemovalOptions.PreserveApplicationData via new preserveAppData parameter. |
| src/winapp-CLI/WinApp.Cli/Services/MsixService.Identity.cs | Plumbs clean into identity creation and uses it to control app-data preservation on unregister. |
| src/winapp-CLI/WinApp.Cli/Services/IPackageRegistrationService.cs | Extends unregister API with preserveAppData. |
| src/winapp-CLI/WinApp.Cli/Services/IMsixService.cs | Extends loose-layout identity API with clean. |
| src/winapp-CLI/WinApp.Cli/Commands/UnregisterCommand.cs | Ensures explicit unregister wipes app data. |
| src/winapp-CLI/WinApp.Cli/Commands/RunCommand.cs | Adds --clean option and passes it through to MSIX service. |
| src/winapp-CLI/WinApp.Cli.Tests/FakePackageRegistrationService.cs | Updates fake signature for new unregister parameter. |
| src/winapp-CLI/WinApp.Cli.Tests/FakeMsixService.cs | Updates fake signature for new clean parameter. |
| docs/usage.md | Documents new default persistence + --clean behavior and examples. |
| docs/npm-usage.md | Documents npm RunOptions.clean. |
| docs/cli-schema.json | Adds --clean to generated CLI schema. |
| .github/plugin/skills/winapp-cli/setup/SKILL.md | Updates skills doc to include --clean option. |
Comments suppressed due to low confidence (2)
src/winapp-CLI/WinApp.Cli/Services/PackageRegistrationService.cs:95
- UnregisterAsync currently matches packages using only
p.Id.Name == packageNameand then removes every match. This can unintentionally target packages from other publishers (including Store/MSIX installs) that share the same identity name. It also meanspreserveAppData=truemay be applied to non-development-mode packages, even thoughRemovalOptions.PreserveApplicationDatais only supported for dev-mode registrations, potentially causing removals to fail or behave unexpectedly. Consider tightening the removal criteria (e.g., require publisher/family name, and/or only removepkg.IsDevelopmentModepackages when preserving data) sowinapp run/unregistercannot affect unrelated installed apps.
public async Task<bool> UnregisterAsync(string packageName, bool preserveAppData = true, CancellationToken cancellationToken = default)
{
var pm = new PackageManager();
// FindPackagesForUser with name+publisher requires both to match.
src/winapp-CLI/WinApp.Cli.Tests/FakeMsixService.cs:28
- FakeMsixService doesn’t record the
cleanargument passed to AddLooseLayoutIdentityAsync, which makes it hard for tests to validate--cleanbehavior end-to-end. Consider storing the full call (includingclean) rather than only the manifest path.
public Task<MsixIdentityResult> AddLooseLayoutIdentityAsync(
FileInfo appxManifestPath,
DirectoryInfo inputDirectory,
DirectoryInfo outputAppXDirectory,
TaskContext taskContext,
bool clean = false,
CancellationToken cancellationToken = default)
{
AddLooseLayoutCalls.Add(appxManifestPath.FullName);
if (ExceptionToThrow != null)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Build Metrics ReportBinary Sizes
Test Results✅ 651 passed out of 651 tests in 337.4s (+4 tests, +14.5s vs. baseline) Test Coverage❌ 30.8% line coverage, 42.4% branch coverage · ✅ no change vs. baseline CLI Startup Time37ms median (x64, Updated 2026-04-06 21:19:56 UTC · commit |
- FakePackageRegistrationService now records (packageName, preserveAppData) tuples so tests can assert on data preservation behavior. - FakeMsixService now records (manifestPath, clean) tuples. - Added option parsing tests for --clean flag. - Added handler tests verifying clean=true/false is passed through to the MSIX service. - Updated existing tests to use new tuple accessors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Problem
winapp runcalledRemovePackageAsyncwithoutRemovalOptions, which wiped the package'sLocalState,RoamingState, andSettingsfolders on every re-deploy. Apps writing toApplicationData.Current.LocalFolderorLocalApplicationDatawould lose all persisted data between runs.This was reported ~15 times as a pain point — developers waste 10-30 minutes debugging why data doesn't persist across restarts.
Fix
UnregisterAsyncnow passesRemovalOptions.PreserveApplicationDataby default when removing dev-mode packages before re-registration. This is a Windows API flag specifically designed for packages registered in development mode.--cleanflag towinapp runfor when a fresh start is needed (e.g., reset corrupted state or test first-run behavior).winapp unregister,--unregister-on-exit) still removes application data, since those are intentional cleanup actions.Changes
PackageRegistrationService.cs,IPackageRegistrationService.csMsixService.Identity.cs,IMsixService.cs,RunCommand.cs,UnregisterCommand.cswinapp-commands.tsFakeMsixService.cs,FakePackageRegistrationService.csusage.md+ autogeneratedcli-schema.json,npm-usage.md,SKILL.mdBehavior matrix
winapp run ./bin/Debug(re-deploy)winapp run ./bin/Debug --cleanwinapp unregisterwinapp run --unregister-on-exit