Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/skills/bump-deps/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
6 changes: 3 additions & 3 deletions .claude/skills/deploy/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<other dir>/` instead).

## 2. Build

Expand All @@ -20,15 +20,15 @@ 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_<version>_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_<version>.0_Test/UnityExtension_<version>.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.

## 3. Install

**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 <output>/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 <bundle>`.
**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 <bundle> -ForceApplicationShutdown`; confirm with `Get-AppxPackage` (new version, `SignatureKind` Developer) and that `UnityExtension.exe` is running from `WindowsApps`.

## 4. Restart and verify

Expand Down
8 changes: 5 additions & 3 deletions .claude/skills/release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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_<X.Y.Z.0>_Test/UnityExtension_<X.Y.Z.0>_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_<X.Y.Z.0>_Test/UnityExtension_<X.Y.Z.0>_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

Expand Down
3 changes: 3 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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 <owner/repo> -s <skill> -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 3 additions & 6 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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=<thumb>`; `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
Expand Down
41 changes: 22 additions & 19 deletions docs/PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/<version>_Test/*.msixbundle`
Output (repo root, git-ignored): `AppPackages/UnityExtension_<version>.0_Test/UnityExtension_<version>.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

Expand All @@ -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=<GUID>`). 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 <bundle> -ForceApplicationShutdown`.

Building with signing on (no `AppxPackageSigningEnabled=false`) fails with `APPX0101` unless you pass `-p:PackageCertificateThumbprint=<thumb>`; `GenerateTemporaryStoreCertificate` in the `.csproj` does not cover the bundle build.

### Store submission

Expand All @@ -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 '*<PublisherGUID>*' } | 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
Expand All @@ -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.
Expand Down Expand Up @@ -126,6 +123,12 @@ One-time setup:

On a `v*` tag the job downloads the built bundle and runs `msstore publish <bundle> -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.
Expand Down
Loading
Loading