Skip to content

Commit 7cf3a23

Browse files
authored
Update Copilot instructions for winget release process and add publish-to-winget skill documentation (#1665)
* Update Copilot instructions for winget release process and add publish-to-winget skill documentation * Address comments
1 parent b73c964 commit 7cf3a23

2 files changed

Lines changed: 248 additions & 1 deletion

File tree

.github/copilot-instructions.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ To create a new plugin:
3434

3535
- **Commit messages:** Clear, succinct, and reference the issue they close when applicable
3636
- **Versioning:** We follow semver. Use the `upgrade-devproxy-version` skill for version bumps
37-
- **Release:** After release builds, homebrew and winget are updated manually
37+
- **Release:** After release builds, homebrew is updated manually. Use the `publish-to-winget` skill for winget submissions
3838

3939
## Testing Code Changes
4040

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
---
2+
name: publish-to-winget
3+
description: >
4+
This skill should be used when the user asks to "publish to winget",
5+
"update winget", "submit to winget", "winget release", "create winget PR",
6+
or mentions publishing Dev Proxy to the Windows Package Manager.
7+
Handles creating manifest files, computing installer hashes, and submitting
8+
a pull request to the winget-pkgs repository.
9+
---
10+
11+
# Publish Dev Proxy to Winget
12+
13+
Create winget manifest files and submit a PR to `microsoft/winget-pkgs` — entirely via the GitHub API. No local clone of winget-pkgs required.
14+
15+
## Prerequisites
16+
17+
Before starting, verify ALL of the following:
18+
19+
1. **`gh` CLI** is installed and authenticated (`gh auth status`)
20+
2. **`curl`** is available
21+
3. **`shasum`** (macOS) or **`sha256sum`** (Linux) is available for computing SHA256 hashes
22+
4. **`awk`** is available
23+
5. **The installer** `.exe` is already published at the GitHub Releases URL for the target version
24+
25+
If any prerequisite fails, stop and tell the user what's missing.
26+
27+
## Input
28+
29+
Ask the user for the **version string** if not provided. Format: `X.Y.Z` or `X.Y.Z-beta.N`.
30+
31+
Validate with pattern: `^(\d+)\.(\d+)\.(\d+)(-beta\.\d+)?$`
32+
33+
## Constants
34+
35+
```
36+
UPSTREAM_REPO = microsoft/winget-pkgs
37+
GITHUB_RELEASE_URL = https://github.com/dotnet/dev-proxy/releases/download/v
38+
MANIFEST_SCHEMA_URL = https://aka.ms/winget-manifest
39+
MANIFEST_VERSION = 1.12.0
40+
```
41+
42+
> **Keeping the schema version current:** The `MANIFEST_VERSION` must match the
43+
> latest schema version used in [winget-pkgs](https://github.com/microsoft/winget-pkgs/tree/master/doc/manifest/schema).
44+
> Check before publishing — if a newer schema exists, update
45+
> `MANIFEST_VERSION` in this file.
46+
47+
## Process
48+
49+
### Step 1: Determine Release Type
50+
51+
Parse the version string to determine if this is a beta release:
52+
53+
| Condition | `releaseFolder` | `packageIdentifier` | `packageName` |
54+
|-----------|-----------------|---------------------|---------------|
55+
| Version contains `beta` | `DevProxy/Beta` | `DevProxy.DevProxy.Beta` | `Dev Proxy Beta` |
56+
| Otherwise | `DevProxy` | `DevProxy.DevProxy` | `Dev Proxy` |
57+
58+
### Step 2: Ensure Fork Exists
59+
60+
```bash
61+
gh repo fork microsoft/winget-pkgs --clone=false
62+
```
63+
64+
Determine the fork name (the authenticated user's fork):
65+
66+
```bash
67+
FORK_REPO=$(gh api user --jq '.login')/winget-pkgs
68+
```
69+
70+
### Step 3: Download Installer and Compute SHA256
71+
72+
Download the installer to a temporary file, validate the download succeeded, then compute the SHA256 hash:
73+
74+
```bash
75+
INSTALLER_URL="${GITHUB_RELEASE_URL}<version>/dev-proxy-installer-win-x64-v<version>.exe"
76+
INSTALLER_FILE=$(mktemp)
77+
curl -fSL -o "${INSTALLER_FILE}" "${INSTALLER_URL}"
78+
```
79+
80+
If `curl` exits with a non-zero status (e.g., HTTP 404), stop and tell the user the installer is not available at that URL.
81+
82+
Compute the hash from the downloaded file:
83+
84+
```bash
85+
SHA256=$(shasum -a 256 "${INSTALLER_FILE}" | awk '{print $1}')
86+
rm -f "${INSTALLER_FILE}"
87+
```
88+
89+
On Linux, use `sha256sum` instead of `shasum -a 256`.
90+
91+
If the hash equals `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` (SHA256 of an empty file), treat this as a failure.
92+
93+
### Step 4: Get Upstream master HEAD SHA
94+
95+
```bash
96+
MASTER_SHA=$(gh api repos/microsoft/winget-pkgs/git/ref/heads/master --jq '.object.sha')
97+
```
98+
99+
Also fetch the tree SHA from the master commit (needed for `base_tree` in Step 6b):
100+
101+
```bash
102+
MASTER_TREE_SHA=$(gh api repos/microsoft/winget-pkgs/git/commits/${MASTER_SHA} --jq '.tree.sha')
103+
```
104+
105+
### Step 5: Create Branch on Fork
106+
107+
Branch name format: `microsoft-devproxy-X-Y-Z` (dots replaced with dashes).
108+
109+
```bash
110+
gh api repos/${FORK_REPO}/git/refs -f ref="refs/heads/<branch-name>" -f sha="${MASTER_SHA}"
111+
```
112+
113+
If the branch already exists (422 error), update it instead:
114+
115+
```bash
116+
gh api repos/${FORK_REPO}/git/refs/heads/<branch-name> -X PATCH -f sha="${MASTER_SHA}" -f force=true
117+
```
118+
119+
### Step 6: Create Manifest Files via Git Data API
120+
121+
Build the manifest file content, then commit all three files in a single commit using the Git Data API.
122+
123+
The manifest path prefix is: `manifests/d/DevProxy/<releaseFolder>/<version>/`
124+
125+
**6a: Create blobs** for each file:
126+
127+
```bash
128+
BLOB_SHA=$(gh api repos/${FORK_REPO}/git/blobs -f content="<file-content>" -f encoding="utf-8" --jq '.sha')
129+
```
130+
131+
Create one blob per manifest file (3 total).
132+
133+
**6b: Create tree** with all three files:
134+
135+
```bash
136+
gh api repos/${FORK_REPO}/git/trees \
137+
-f "base_tree=${MASTER_TREE_SHA}" \
138+
-f "tree[][path]=manifests/d/DevProxy/<releaseFolder>/<version>/<packageIdentifier>.installer.yaml" \
139+
-f "tree[][mode]=100644" \
140+
-f "tree[][type]=blob" \
141+
-f "tree[][sha]=<installer-blob-sha>" \
142+
-f "tree[][path]=manifests/d/DevProxy/<releaseFolder>/<version>/<packageIdentifier>.locale.en-US.yaml" \
143+
-f "tree[][mode]=100644" \
144+
-f "tree[][type]=blob" \
145+
-f "tree[][sha]=<locale-blob-sha>" \
146+
-f "tree[][path]=manifests/d/DevProxy/<releaseFolder>/<version>/<packageIdentifier>.yaml" \
147+
-f "tree[][mode]=100644" \
148+
-f "tree[][type]=blob" \
149+
-f "tree[][sha]=<version-blob-sha>"
150+
```
151+
152+
**6c: Create commit**:
153+
154+
```bash
155+
COMMIT_SHA=$(gh api repos/${FORK_REPO}/git/commits \
156+
-f message="New version: <packageIdentifier> version <version>" \
157+
-f "tree=<tree-sha>" \
158+
-f "parents[]=${MASTER_SHA}" \
159+
--jq '.sha')
160+
```
161+
162+
**6d: Update branch ref**:
163+
164+
```bash
165+
gh api repos/${FORK_REPO}/git/refs/heads/<branch-name> -X PATCH -f sha="${COMMIT_SHA}"
166+
```
167+
168+
### Manifest File Content
169+
170+
Use the exact content below, substituting variables.
171+
172+
**File: `<packageIdentifier>.installer.yaml`**
173+
174+
```yaml
175+
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.<MANIFEST_VERSION>.schema.json
176+
177+
PackageIdentifier: <packageIdentifier>
178+
PackageVersion: <version>
179+
InstallerType: inno
180+
Installers:
181+
- InstallerUrl: https://github.com/dotnet/dev-proxy/releases/download/v<version>/dev-proxy-installer-win-x64-v<version>.exe
182+
Architecture: x64
183+
InstallerSha256: <sha256-hash>
184+
ManifestType: installer
185+
ManifestVersion: <MANIFEST_VERSION>
186+
```
187+
188+
**File: `<packageIdentifier>.locale.en-US.yaml`**
189+
190+
```yaml
191+
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.<MANIFEST_VERSION>.schema.json
192+
193+
PackageIdentifier: <packageIdentifier>
194+
PackageVersion: <version>
195+
PackageLocale: en-US
196+
Publisher: .NET Foundation
197+
PackageName: <packageName>
198+
License: MIT License
199+
ShortDescription: <shortDescription>
200+
ManifestType: defaultLocale
201+
ManifestVersion: <MANIFEST_VERSION>
202+
```
203+
204+
> **ShortDescription rules:** Must be 3–256 characters. Must NOT be just
205+
> "package name installer" or "package name setup". Use a real description.
206+
>
207+
> | Release type | `shortDescription` |
208+
> |-------------|---------------------|
209+
> | Stable | `Dev Proxy is a command line tool for simulating APIs for testing apps` |
210+
> | Beta | `Dev Proxy Beta is a preview build of the command line tool for simulating APIs for testing apps` |
211+
212+
**File: `<packageIdentifier>.yaml`**
213+
214+
```yaml
215+
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.<MANIFEST_VERSION>.schema.json
216+
217+
PackageIdentifier: <packageIdentifier>
218+
PackageVersion: <version>
219+
DefaultLocale: en-US
220+
ManifestType: version
221+
ManifestVersion: <MANIFEST_VERSION>
222+
```
223+
224+
### Step 7: Create Pull Request
225+
226+
```bash
227+
FORK_OWNER=$(gh api user --jq '.login')
228+
gh pr create \
229+
--repo microsoft/winget-pkgs \
230+
--head "${FORK_OWNER}:<branch-name>" \
231+
--base master \
232+
--title "New version: <packageIdentifier> version <version>" \
233+
--body "New version: <packageIdentifier> version <version>"
234+
```
235+
236+
Show the user the PR URL when done.
237+
238+
## Troubleshooting
239+
240+
| Problem | Solution |
241+
|---------|----------|
242+
| `gh` not found | Install with `brew install gh` then `gh auth login` |
243+
| `gh auth` not logged in | Run `gh auth login` |
244+
| Installer 404 | Verify the release exists at `https://github.com/dotnet/dev-proxy/releases/tag/v<version>` |
245+
| Empty SHA256 hash | The curl download failed — check the URL and your network |
246+
| Branch already exists (422) | The script handles this by force-updating the existing branch |
247+
| Fork already exists | `gh repo fork` is idempotent — safe to re-run |

0 commit comments

Comments
 (0)