|
| 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