From f48f5a50ddf84272f5117d5fc9b5d78b0bf46967 Mon Sep 17 00:00:00 2001 From: Yidi Mao Date: Fri, 16 Oct 2026 05:41:08 +0300 Subject: [PATCH] docs: fix build, signing and release guidance found during the 0.1.4 release - Replace the bundle build command that fails locally (APPX0101) with the CI-equivalent restore + unsigned build; document the repo-root AppPackages output, elevated cert trust for sideloading, and the sign.ps1 -Subject need - Document NETSDK1094 (win-*.pubxml must match) and Store API draft gotchas - Update CLAUDE.md: msstore is configured, ci.yml/store-publish layout - release/deploy/bump-deps skills: pre-tag checklist, bundle build in validation, no force-kill of the running extension - pre-commit: exclude vendored skills/rules from whitespace hooks --- .claude/skills/bump-deps/SKILL.md | 2 +- .claude/skills/deploy/SKILL.md | 6 ++--- .claude/skills/release/SKILL.md | 8 +++--- .pre-commit-config.yaml | 3 +++ CLAUDE.md | 8 ++++-- docs/DEVELOPMENT.md | 9 +++---- docs/PUBLISHING.md | 41 +++++++++++++++++-------------- docs/TROUBLESHOOTING.md | 11 +++++++++ 8 files changed, 54 insertions(+), 34 deletions(-) diff --git a/.claude/skills/bump-deps/SKILL.md b/.claude/skills/bump-deps/SKILL.md index fe4aa75..8ad6fe0 100644 --- a/.claude/skills/bump-deps/SKILL.md +++ b/.claude/skills/bump-deps/SKILL.md @@ -66,7 +66,7 @@ dotnet build UnityExtension/UnityExtension.csproj -c Release -p:Platform=x64 -p: dotnet tool run csharpier check . ``` -Run the Release build whenever WindowsAppSDK or CsWinRT changed; it is what catches trim/AOT regressions. If an upgrade breaks the build, pin one step back and say why. Loading the result in PowerToys (`deploy` skill) is the only check that proves runtime compatibility; say when it was not done. +Run the Release build whenever WindowsAppSDK or CsWinRT changed; it is what catches trim/AOT regressions. Also run the MSIX bundle build from `docs/PUBLISHING.md` ("Package build"): PR CI never builds the bundle, and packaging problems (publish profiles, runtime packs) only show up there. If an upgrade breaks the build, pin one step back and say why. Loading the result in PowerToys (`deploy` skill) is the only check that proves runtime compatibility; say when it was not done. ## Report diff --git a/.claude/skills/deploy/SKILL.md b/.claude/skills/deploy/SKILL.md index ba65cef..cf848c6 100644 --- a/.claude/skills/deploy/SKILL.md +++ b/.claude/skills/deploy/SKILL.md @@ -10,7 +10,7 @@ Two modes. Default to **debug** for iteration; use **release** only when signing ## 1. Stop PowerToys and the extension -Locked files make register/install fail. Close `PowerToys`, `Microsoft.CmdPal.UI` and `UnityExtension` processes (graceful close, then `Stop-Process -Force`). +Locked files make register/install fail. For a release install, `Add-AppxPackage ... -ForceApplicationShutdown` closes the running extension itself. For a debug register, close `PowerToys`, `Microsoft.CmdPal.UI` and `UnityExtension` gracefully first; use `Stop-Process -Force` only with the user's OK (project rule: never kill the running extension just to compile-check, use `-p:OutDir=/` instead). ## 2. Build @@ -20,7 +20,7 @@ Debug (needs `-p:WindowsPackageType=MSIX`, otherwise the csproj defaults to `Non dotnet build UnityExtension/UnityExtension.csproj -c Debug -p:Platform=x64 -p:WindowsPackageType=MSIX ``` -Release bundle: use the command in `docs/PUBLISHING.md` ("Package build"). Output: `AppPackages/UnityExtension__Test/*.msixbundle`. For Store-signed builds see "Store submission" in the same doc; the private identity values are in the git-ignored `docs/store-identity.md`. +Release bundle: use the restore + `--no-restore` unsigned command in `docs/PUBLISHING.md` ("Package build"); it mirrors CI. Output (repo root): `AppPackages/UnityExtension_.0_Test/UnityExtension_.0_x64_arm64.msixbundle`. A signed build without a thumbprint fails with `APPX0101`; `NETSDK1094` means the two `win-*.pubxml` profiles disagree (see `docs/TROUBLESHOOTING.md`). For Store-signed builds see "Store submission" in the same doc; the private identity values are in the git-ignored `docs/store-identity.md`. Stop and report if the build fails; do not deploy a stale output. @@ -28,7 +28,7 @@ Stop and report if the build fails; do not deploy a stale output. **Debug.** A release package of the same identity blocks `-Register` (error `0x80073CFB`), so remove any installed one first (`Get-AppxPackage *CommandPalette-Unity* | Remove-AppxPackage`). Copy `UnityExtension/Assets/*` into `Assets/` next to the output, then `Add-AppxPackage -Register /AppxManifest.xml -ForceUpdateFromAnyVersion`. The manifest is under `UnityExtension/bin/x64/Debug/net10.0-windows10.0.26100.0/win-x64/`; locate it with a glob rather than trusting the TFM. -**Release.** Sign the bundle with the git-ignored `scripts/sign.ps1` (read its `param()` block for options; pass `-TrustStore CurrentUser` when not elevated and `-BundlePath` explicitly, because auto-detect can pick a per-arch `.msix`). Then remove the old package and `Add-AppxPackage -Path `. +**Release.** Sign the bundle with the git-ignored `scripts/sign.ps1` (read its `param()` block). Pass `-BundlePath` explicitly (auto-detect can pick a per-arch `.msix`) and `-Subject` equal to the manifest `Publisher` (the script's default `CN=Yidi Mao` fails its own check). Trusting the cert in `LocalMachine\TrustedPeople` and verifying the signature need an **elevated** shell; `-TrustStore CurrentUser` signs but then fails verification ("root certificate not trusted"). An agent session is not elevated, so hand the user the exact command to run in an admin terminal. Then `Get-AppxPackage *CommandPalette-Unity* | Remove-AppxPackage` and `Add-AppxPackage -Path -ForceApplicationShutdown`; confirm with `Get-AppxPackage` (new version, `SignatureKind` Developer) and that `UnityExtension.exe` is running from `WindowsApps`. ## 4. Restart and verify diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 949a674..8dee2dc 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -6,7 +6,7 @@ description: >- # Release -The tag triggers `.github/workflows/release-msix.yml`, which validates versions, builds, signs with the CI secret and creates the GitHub release with the bundle and `.cer`. Local signing is only for sideload testing. +The tag triggers `.github/workflows/release-msix.yml`, which validates versions, builds, signs with the CI secret and creates the GitHub release with the bundle and `.cer`. When repo variable `STORE_PUBLISH` is `true`, the `store-publish` job then waits for reviewer approval in the `microsoft-store` environment and submits the same bundle with `msstore` (details and gotchas in `docs/PUBLISHING.md`). Local signing is only for sideload testing. ## 1. Sync the version @@ -23,11 +23,13 @@ The fourth segment stays `.0` (Store rule). Then grep tracked files (not `bin/`, ## 2. Validate -Delete `bin`, `obj` and `AppPackages` if identity or name changed, then run the release bundle build from `docs/PUBLISHING.md`. A bundle at `AppPackages/UnityExtension__Test/UnityExtension__x64_arm64.msixbundle` means it worked. Optionally sideload-test it via the `deploy` skill. +Delete `bin`, `obj` and `AppPackages` if identity, name or publish profiles changed, then run the restore + `--no-restore` unsigned bundle build from `docs/PUBLISHING.md` (the PR CI job does not build the bundle, so this is the only pre-tag packaging check). A bundle at repo-root `AppPackages/UnityExtension__Test/UnityExtension__x64_arm64.msixbundle` means it worked. Optionally sideload-test it via the `deploy` skill. + +The version must be higher than the one last published to the Store (`winget show` does not report it; ask the user or check Partner Center), and no draft may be open in the Partner Center web UI. ## 3. Commit and tag -Commit the version change (Conventional Commits, e.g. `chore: bump version to X.Y.Z`). Pushing the tag publishes a public release and triggers CI, so get explicit user approval before `git push` and `git push origin vX.Y.Z`. Check `git remote get-url origin` first. Afterwards watch the run with `gh run watch`. +Commit the version change on a branch (Conventional Commits, e.g. `chore: bump version to X.Y.Z`; mention notable merged PRs the release ships), open a PR and have it merged, then tag `master` so the tag commit contains the release workflow as it should run. Pushing the tag publishes a public release, triggers CI and, with `STORE_PUBLISH=true`, queues a Store submission, so get explicit user approval before `git push` and `git push origin vX.Y.Z`. Check `git remote get-url origin` first. Afterwards watch the run with `gh run watch`; the `store-publish` job pauses for the user's approval in GitHub, which the agent does not give. ## Report diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f7b30b1..f03b944 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,8 +4,11 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 hooks: + # Vendored skills and rules are kept byte-for-byte as installed (lock hashes in skills-lock.json). - id: trailing-whitespace + exclude: ^\.claude/(skills/(add-dock-band|add-extension-settings|microsoft-code-reference|microsoft-docs|msstore-cli|publish-extension)|rules)/ - id: end-of-file-fixer + exclude: ^\.claude/(skills/(add-dock-band|add-extension-settings|microsoft-code-reference|microsoft-docs|msstore-cli|publish-extension)|rules)/ - id: check-merge-conflict - id: check-case-conflict - id: check-json diff --git a/CLAUDE.md b/CLAUDE.md index 4ab7172..d0c47e0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,8 @@ Published on the Microsoft Store as "Project Launcher for Unity", product ID `9N - `UnityExtension/` = C# project (`Commands/`, `Data/`, `Helpers/`, `Pages/`, `Program.cs`, `UnityExtension.cs`, `Package.appxmanifest`) - `docs/` = DEVELOPMENT, PUBLISHING, TROUBLESHOOTING (+ local-only, git-ignored `store-identity.md`); screenshots in `docs/assets/` -- `.github/workflows/release-msix.yml` = tag `v*` -> validate versions -> build -> sign -> GitHub release +- `.github/workflows/ci.yml` = PR checks (format, unpackaged Release build, tests, version agreement); it does not build the MSIX bundle +- `.github/workflows/release-msix.yml` = tag `v*` -> validate versions -> build -> sign -> GitHub release -> opt-in `store-publish` (repo var `STORE_PUBLISH`, environment `microsoft-store` needs reviewer approval) - `Directory.Packages.props` = all package versions; `dotnet-tools.json` = csharpier, minver-cli; `.pre-commit-config.yaml` = hooks - No Markdown under `UnityExtension/`; contributor guidance -> `CONTRIBUTING.md`; user docs -> `docs/` - Local-only (git-ignored, do not commit): `.claude/settings.local.json`, `docs/store-identity.md`, `scripts/` (`sign.ps1`, `pfx.ps1`, Unity path helpers) @@ -29,7 +30,7 @@ Published on the Microsoft Store as "Project Launcher for Unity", product ID `9N - Third-party skills (managed by `bunx skills`, tracked in `skills-lock.json`; never hand-edit or refactor): `microsoft-docs`, `microsoft-code-reference` (`MicrosoftDocs/mcp`); `msstore-cli` (`github/awesome-copilot`); `publish-extension`, `add-extension-settings`, `add-dock-band` (official PowerToys extension template) - Add: `bunx skills add -s -a claude-code --copy -y`; update: `bunx skills update -p -y`; list: `bunx skills list`; restore: `bunx skills experimental_install` - Rule `.claude/rules/cmdpal-extension.md` (vendored from the PowerToys extension template, path-scoped to `UnityExtension/**/*.cs`; never hand-edit): Command Palette API reference and common mistakes -- CLIs (winget): `winapp` (Microsoft.WinAppCli: `run`/`unregister` dev registration, `cert`, `package`, `sign`, `ui` inspects the live Command Palette window) `actionlint` (also a pre-commit hook for `.github/workflows/`), and `msstore` (Store submissions; needs Partner Center credentials via `msstore reconfigure`, not configured locally; see skill `msstore-cli`) +- CLIs (winget): `winapp` (Microsoft.WinAppCli: `run`/`unregister` dev registration, `cert`, `package`, `sign`, `ui` inspects the live Command Palette window) `actionlint` (also a pre-commit hook for `.github/workflows/`), and `msstore` (Store submissions; configured locally via `msstore reconfigure` with flags, credentials live in the CLI's own store, never in the repo; see skill `msstore-cli` and `docs/PUBLISHING.md`) - Recent Learn docs cover newer SDK surface (e.g. Dock bands); check them before assuming an API is missing ## Code rules @@ -62,6 +63,9 @@ dotnet build UnityExtension/UnityExtension.csproj -c Release -p:Platform=x64 -p: - Identity `Name`/`Publisher` must match Partner Center exactly - After identity/name changes delete `bin`, `obj`, `AppPackages` before rebuilding - Debug builds get `.Debug` package suffix and `(Dev)` display name; a release package blocks debug `-Register` (0x80073CFB), remove it first +- Bundle builds: restore first, then `--no-restore ... -p:AppxPackageSigningEnabled=false` (exact command in `docs/PUBLISHING.md`); `win-x64.pubxml` and `win-arm64.pubxml` must set the same properties or the build fails with `NETSDK1094`; output lands in repo-root `AppPackages/` +- Local signing/sideload of a bundle needs an elevated shell (cert trusted in `LocalMachine\TrustedPeople`); `scripts/sign.ps1` needs `-Subject` set to the manifest Publisher +- `msstore` cannot touch a draft created in the Partner Center web UI; `msstore publish` discards any existing API draft - `Package.appxmanifest` `MaxVersionTested` is still 10.0.19041.0; only change via `bump-deps` Step 4 ## Do not diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index ecd396b..18db003 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -7,11 +7,7 @@ dotnet build UnityExtension/UnityExtension.csproj -c Debug -p:Platform=x64 dotnet build UnityExtension/UnityExtension.csproj -c Release -p:Platform=x64 ``` -For packaged MSIX output (local dev — no thumbprint needed, `GenerateTemporaryStoreCertificate` in csproj handles it): - -```bash -dotnet build UnityExtension/UnityExtension.csproj -c Release -p:Platform=x64 -p:GenerateAppxPackageOnBuild=true -p:AppxBundle=Always -p:AppxBundlePlatforms="x64|arm64" -p:SuppressSdkNoise=true -``` +For packaged MSIX output (restore first, build unsigned, sign afterwards), see "Package build" and "Signing" in [`PUBLISHING.md`](PUBLISHING.md#package-build). A plain signed `dotnet build ... -p:GenerateAppxPackageOnBuild=true` fails with `APPX0101` without a certificate thumbprint. Trim / AOT notes: @@ -43,7 +39,8 @@ Watch VS Output window, use temp logging if needed. - Keep `UnityExtension.cs` + `Package.appxmanifest` aligned - CLSID must match in COM server + app extension registration - Package identity (`Name`, `Publisher`) must match Partner Center reserved values -- `GenerateTemporaryStoreCertificate=True` in csproj for local dev; pass `PackageCertificateThumbprint` for Store-signing cert +- Bundle builds need either `-p:AppxPackageSigningEnabled=false` (then sign the bundle) or `-p:PackageCertificateThumbprint=`; `GenerateTemporaryStoreCertificate` alone is not enough +- `Properties/PublishProfiles/win-x64.pubxml` and `win-arm64.pubxml` must stay in sync (e.g. both set or both omit `PublishReadyToRun`) - If `GenerateAppxPackageOnBuild=true`, do not force `WindowsPackageType=None` - Local deployment: `Add-AppxPackage -Register` against debug `AppxManifest.xml` - Prefer current Microsoft Learn packaging guidance over old repo scripts diff --git a/docs/PUBLISHING.md b/docs/PUBLISHING.md index 81b88c9..6cf43af 100644 --- a/docs/PUBLISHING.md +++ b/docs/PUBLISHING.md @@ -6,29 +6,24 @@ Use latest Microsoft Learn / PowerToys docs for packaging + publishing. ## Package build +This is the same sequence CI runs. Restore first, then build with `--no-restore`; the unsigned build avoids `APPX0101` ("A signing key is required"), and you sign afterwards. + ```bash -dotnet build UnityExtension/UnityExtension.csproj \ +dotnet restore UnityExtension/UnityExtension.csproj +dotnet build UnityExtension/UnityExtension.csproj --no-restore \ -c Release -p:Platform=x64 \ -p:GenerateAppxPackageOnBuild=true \ -p:AppxBundle=Always \ -p:AppxBundlePlatforms="x64|arm64" \ - -p:SuppressSdkNoise=true + -p:AppxPackageSigningEnabled=false \ + -p:SuppressSdkNoise=false ``` -Output: `AppPackages/_Test/*.msixbundle` +Output (repo root, git-ignored): `AppPackages/UnityExtension_.0_Test/UnityExtension_.0_x64_arm64.msixbundle` -For full trim/AOT warnings before submission: +`win-x64.pubxml` and `win-arm64.pubxml` must set the same properties. If only one sets `PublishReadyToRun`, restore skips the other architecture's ReadyToRun runtime pack and the build fails with `NETSDK1094`. -```bash -# omit SuppressSdkNoise and add TrimmerSingleWarn=false -dotnet build UnityExtension/UnityExtension.csproj \ - -c Release -p:Platform=x64 \ - -p:GenerateAppxPackageOnBuild=true \ - -p:AppxBundle=Always \ - -p:AppxBundlePlatforms="x64|arm64" \ - -p:SuppressSdkNoise=false \ - -p:TrimmerSingleWarn=false -``` +Add `-p:TrimmerSingleWarn=false` to see every trim/AOT warning before submission. To sign for sideloading, see "Signing" below. ## Package identity @@ -49,7 +44,9 @@ dotnet build UnityExtension/UnityExtension.csproj \ ### Local dev -`GenerateTemporaryStoreCertificate=True` in `.csproj` auto-creates a temp cert matching the manifest `Publisher`. No thumbprint needed for `dotnet build`. +Build unsigned (see "Package build"), then sign the bundle with a self-signed cert whose `Subject` equals the manifest `Publisher` exactly (`CN=`). Windows only installs it if that cert is trusted in `Cert:\LocalMachine\TrustedPeople`; importing there and verifying the signature both need an **elevated** shell (`CurrentUser` trust is not enough). Sideload with `Add-AppxPackage -Path -ForceApplicationShutdown`. + +Building with signing on (no `AppxPackageSigningEnabled=false`) fails with `APPX0101` unless you pass `-p:PackageCertificateThumbprint=`; `GenerateTemporaryStoreCertificate` in the `.csproj` does not cover the bundle build. ### Store submission @@ -65,11 +62,11 @@ New-SelfSignedCertificate -Type Custom -KeyUsage DigitalSignature ` -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.3","2.5.29.19={text}") ``` -Build with matching cert: +Build with matching cert (or build unsigned and sign afterwards, as above): ```powershell $thumb = Get-ChildItem Cert:\CurrentUser\My | Where-Object { $_.Subject -like '**' } | Select-Object -First 1 -ExpandProperty Thumbprint -dotnet build UnityExtension/UnityExtension.csproj -c Release -p:Platform=x64 -p:GenerateAppxPackageOnBuild=true -p:AppxBundle=Always -p:AppxBundlePlatforms="x64|arm64" -p:SuppressSdkNoise=true -p:PackageCertificateThumbprint=$thumb +dotnet build UnityExtension/UnityExtension.csproj --no-restore -c Release -p:Platform=x64 -p:GenerateAppxPackageOnBuild=true -p:AppxBundle=Always -p:AppxBundlePlatforms="x64|arm64" -p:SuppressSdkNoise=true -p:PackageCertificateThumbprint=$thumb ``` ## Install for testing @@ -80,10 +77,10 @@ Remove old package first: Get-AppxPackage *CommandPalette-Unity* | Remove-AppxPackage ``` -Install the bundle: +Install the signed bundle (the cert must be trusted, see "Signing"): ```powershell -Add-AppxPackage -Path "AppPackages\UnityExtension_*\UnityExtension_*.msixbundle" +Add-AppxPackage -Path "AppPackages\UnityExtension_*\UnityExtension_*.msixbundle" -ForceApplicationShutdown ``` Reload Command Palette or restart PowerToys after install. @@ -126,6 +123,12 @@ One-time setup: On a `v*` tag the job downloads the built bundle and runs `msstore publish -id 9NVSJZ72LTZ3`, then `msstore submission poll`. The Store re-signs the package, so the self-signed PFX does not matter. Only free apps are supported by the CLI; the version must be higher than the last published one, so bump the version before tagging. A `.msixbundle` path works with `publish` (verified with `--noCommit`). +Before tagging a release: + +1. Bump the version via a PR and merge it, so `master` carries both the new version and the `store-publish` job. +2. Make sure no draft is open in the Partner Center web UI, and that the new version is higher than the last published one. +3. Tag `vX.Y.Z` from `master`. The job pauses in the `microsoft-store` environment until a required reviewer approves; rejecting it skips the Store but keeps the GitHub release. + Gotchas: - The API cannot touch a draft created in the Partner Center web UI. If one is open, `publish` stops on "Ingestion API can only update, delete, and commit submissions that are created through the API", and in CI it would hang. Delete or submit the web draft first, and do not leave drafts open in the portal. diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md index 737e2cd..f39b673 100644 --- a/docs/TROUBLESHOOTING.md +++ b/docs/TROUBLESHOOTING.md @@ -42,6 +42,17 @@ If Release packaging fails with `WindowsPackageType=None` + `GenerateAppxPackage If packaging fails with `IL2104` / `NETSDK1144`, check trimming enabled for packaging build path, temporarily disable if needed. +## Bundle build errors + +- `APPX0101` "A signing key is required": pass `-p:AppxPackageSigningEnabled=false` (sign afterwards) or `-p:PackageCertificateThumbprint=`. See "Package build" in [`PUBLISHING.md`](PUBLISHING.md#package-build). +- `NETSDK1094` "Unable to optimize assemblies for performance": the two publish profiles disagree on `PublishReadyToRun`, so restore skipped one architecture's runtime pack. Make `win-x64.pubxml` and `win-arm64.pubxml` match, delete `bin`/`obj`/`AppPackages`, then restore and build again. The ILLink crash (exit code `-1073741819`) showed up in the same state. +- Signing verification says "root certificate not trusted" or install fails: trust the cert in `Cert:\LocalMachine\TrustedPeople` from an elevated shell; `CurrentUser` is not enough. + +## Store publishing errors + +- "Ingestion API can only update, delete, and commit submissions that are created through the API": a draft made in the Partner Center web UI blocks `msstore`. Delete or submit it in the portal first (CI would hang on the browser prompt). +- `msstore reconfigure` "Error while retrieving Organization": use the flag form (`--tenantId --sellerId --clientId --clientSecret`) instead of the interactive prompts. See [`PUBLISHING.md`](PUBLISHING.md#microsoft-store-updates-automated). + ## Build fails: file is locked `MSB3021` / `MSB3027` "locked by: Unity Extension for Command Palette" means the extension is running inside Command Palette and holds `UnityExtension.exe`. It is not a code error.