From f9beada00b35117d0da9a554ed7bb6b235c9e37e Mon Sep 17 00:00:00 2001 From: John Smith Date: Mon, 15 Sep 2025 11:14:00 +0100 Subject: [PATCH 1/2] docs(blog): add building winget search post MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✨ Add a new blog post describing the Winget Search web interface and how it was built to simplify finding winget package IDs 📁 Added: _posts/2025-09-15-building-winget-search-a-fast-web-interface-for-winget-packages.md 💡 Covers Python manifest extraction, client-side JS search, and GitHub Actions automation for daily updates 🎯 Motivation: speed up package discovery and provide copy-ready winget install commands for faster machine setup --- ...-fast-web-interface-for-winget-packages.md | 236 ++++++++++++++++++ 1 file changed, 236 insertions(+) create mode 100644 _posts/2025-09-15-building-winget-search-a-fast-web-interface-for-winget-packages.md diff --git a/_posts/2025-09-15-building-winget-search-a-fast-web-interface-for-winget-packages.md b/_posts/2025-09-15-building-winget-search-a-fast-web-interface-for-winget-packages.md new file mode 100644 index 00000000..29f2df3f --- /dev/null +++ b/_posts/2025-09-15-building-winget-search-a-fast-web-interface-for-winget-packages.md @@ -0,0 +1,236 @@ +--- +published: true +layout: post +title: Building Winget Search - A fast web interface for Windows Package Manager +description: >- + How I built a GitHub Pages-hosted search interface for winget packages to solve my machine setup workflow +tags: + - python + - javascript + - winget + - github-actions + - github-pages +--- + +## Background + +When setting up a new Windows machine, I used to rely on [Scoop](https://scoop.sh/) and [Chocolatey](https://chocolatey.org/) for package management. Both are excellent tools, but when Microsoft introduced [Windows Package Manager (winget)](https://learn.microsoft.com/en-us/windows/package-manager/), I decided to give it a try on my latest machine setup. + +The problem? Finding winget package IDs was tedious. While `winget search` works, I wanted something faster - a web interface where I could quickly search, find packages, and copy installation commands. That's how [winget-search](https://github.com/solrevdev/winget-search) was born. + +![Winget Search Interface](https://i.imgur.com/YLysmmV.png) + +## The Challenge + +Winget packages are stored in Microsoft's [winget-pkgs repository](https://github.com/microsoft/winget-pkgs) with over 30,000 YAML manifest files. The repository structure follows a pattern: `manifests/publisher/package_name/version/` with separate files for different locales and installers. + +I needed to: +- Extract package metadata from thousands of YAML files +- Handle multiple versions and keep only the latest +- Filter for English descriptions only +- Build a fast, searchable web interface +- Keep data fresh with automated updates + +## The Solution + +### Architecture Overview + +I built a three-part system: + +1. **Python extraction script** - Parses YAML manifests and generates JSON +2. **Static HTML search interface** - Client-side search with instant results +3. **GitHub Actions automation** - Daily updates and deployment + +### Data Extraction + +The Python script (`extract_packages.py`) does the heavy lifting: + +```python +def extract_package_info(manifest_dir): + """Extract comprehensive package info from a manifest directory""" + package_info = { + "id": None, + "name": None, + "description": None, + "publisher": None, + "version": None, + "tags": [], + "homepage": None, + "license": None + } + + # Process version manifest first + version_file = next((f for f in yaml_files if not '.locale.' in f), None) + if version_file: + with open(os.path.join(manifest_dir, version_file), encoding="utf-8") as f: + doc = yaml.safe_load(f) + package_info["id"] = doc.get("PackageIdentifier") + package_info["version"] = doc.get("PackageVersion") + + # Look for English locale file + locale_file = next((f for f in yaml_files if '.locale.en-US.' in f), None) + if locale_file: + with open(os.path.join(manifest_dir, locale_file), encoding="utf-8") as f: + doc = yaml.safe_load(f) + package_info["name"] = doc.get("PackageName") + package_info["publisher"] = doc.get("Publisher") + package_info["description"] = doc.get("Description") +``` + +The script handles version comparison using Python's `packaging` library to ensure we only keep the latest version of each package: + +```python +def parse_version(ver_str): + """Parse version string for proper comparison""" + try: + return version.parse(ver_str) + except: + return version.parse("0.0.0") # Fallback for non-standard versions +``` + +### Web Interface + +The search interface is pure vanilla JavaScript - no frameworks needed. It loads a ~5MB JSON file containing all package data and performs client-side search for instant results: + +```javascript +function showResults(query) { + const q = query.trim().toLowerCase(); + + let results = packages.filter(pkg => { + const idMatch = pkg.id?.toLowerCase().includes(q); + const nameMatch = pkg.name?.toLowerCase().includes(q); + const descMatch = pkg.description?.toLowerCase().includes(q); + const publisherMatch = pkg.publisher?.toLowerCase().includes(q); + const tagMatch = pkg.tags?.some(tag => + tag && typeof tag === 'string' && tag.toLowerCase().includes(q) + ); + + return idMatch || nameMatch || descMatch || publisherMatch || tagMatch; + }).slice(0, 100); + + // Render results... +} +``` + +Each search result includes a one-click copy button for the winget install command: + +```javascript +function copyCommand(button, cmd) { + navigator.clipboard.writeText(cmd).then(() => { + button.innerHTML = 'Copied!'; + setTimeout(() => button.innerHTML = 'Copy', 2000); + }); +} +``` + +### Automation with GitHub Actions + +The magic happens in the GitHub Actions workflow that runs daily at 2 AM UTC: + +```yaml +name: Build and Deploy +on: + schedule: + - cron: '0 2 * * *' # Daily at 2 AM UTC + workflow_dispatch: + +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.11' + + - name: Cache winget-pkgs + uses: actions/cache@v4 + with: + path: winget-pkgs + key: winget-pkgs-${{ github.run_id }} + restore-keys: winget-pkgs- + + - name: Clone winget-pkgs repository + run: | + if [ ! -d "winget-pkgs" ]; then + git clone --depth 1 https://github.com/microsoft/winget-pkgs.git + else + cd winget-pkgs && git pull origin main + fi + + - name: Extract packages + run: python extract_packages.py winget-pkgs/manifests/ packages.json + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: . +``` + +## Technical Highlights + +### Performance Optimizations + +- **Client-side search**: No server required, instant results +- **Debounced input**: 300ms delay prevents excessive filtering +- **Result limiting**: Shows max 100 results for smooth scrolling +- **Caching**: GitHub Actions caches the large winget-pkgs repository + +### User Experience Features + +- **Keyboard shortcuts**: Press `/` to focus search, `Esc` to clear +- **Dark mode**: Automatic theme based on system preferences +- **Mobile-friendly**: Responsive design works on all devices +- **Copy feedback**: Visual confirmation when commands are copied + +### Data Quality + +- **English-only**: Filters for `.locale.en-US.yaml` files +- **Latest versions**: Semantic version comparison ensures freshness +- **Type safety**: Handles edge cases like non-string tags +- **Error resilience**: Continues processing even if individual manifests fail + +## Deployment + +The entire deployment is automated through GitHub Pages. The workflow: + +1. Clones the microsoft/winget-pkgs repository (1GB+, hence the caching) +2. Extracts package data using Python script +3. Generates a `packages.json` file with ~30,000 packages +4. Deploys everything to GitHub Pages using `peaceiris/actions-gh-pages` + +No manual intervention needed - just push code and GitHub Actions handles the rest! + +## Results + +The end result is a fast, searchable interface hosted at GitHub Pages that: + +- Loads 30,000+ packages in under 3 seconds +- Provides instant search results +- Generates copy-ready `winget install` commands +- Updates automatically every day +- Costs nothing to host + +Perfect for when you need to quickly find that package ID for your setup scripts! + +## Future Improvements + +Some ideas I'm considering: + +- **Package categories** - Group by software type +- **Fuzzy search** - Better matching for typos +- **PowerShell commands** - Alternative to cmd syntax +- **Package details modal** - Show more metadata +- **Search history** - Remember recent searches + +## Live Demo + +Check out the live site: [https://solrevdev.github.io/winget-search/](https://solrevdev.github.io/winget-search/) + +The source code is available on [GitHub](https://github.com/solrevdev/winget-search) under the MIT license. + +Success 🎉 \ No newline at end of file From fa6a5771c57cb53fcfb7d2c91f48a156d272d1d7 Mon Sep 17 00:00:00 2001 From: John Smith Date: Mon, 29 Sep 2025 11:52:22 +0100 Subject: [PATCH 2/2] feat(blog): enhance winget search post with accurate cover image and code samples MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove irrelevant placeholder image from blog post body - Add accurate cover image matching real winget-search interface design - Update JavaScript code samples to reflect actual repository implementation - Replace fancy gradient design with clean, minimal interface representation - Improve technical accuracy of blog content 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- ...-fast-web-interface-for-winget-packages.md | 45 ++++++++++------ images/winget-search-cover.svg | 52 +++++++++++++++++++ 2 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 images/winget-search-cover.svg diff --git a/_posts/2025-09-15-building-winget-search-a-fast-web-interface-for-winget-packages.md b/_posts/2025-09-15-building-winget-search-a-fast-web-interface-for-winget-packages.md index 29f2df3f..37cbc2c3 100644 --- a/_posts/2025-09-15-building-winget-search-a-fast-web-interface-for-winget-packages.md +++ b/_posts/2025-09-15-building-winget-search-a-fast-web-interface-for-winget-packages.md @@ -4,6 +4,7 @@ layout: post title: Building Winget Search - A fast web interface for Windows Package Manager description: >- How I built a GitHub Pages-hosted search interface for winget packages to solve my machine setup workflow +cover_image: /images/winget-search-cover.svg tags: - python - javascript @@ -18,8 +19,6 @@ When setting up a new Windows machine, I used to rely on [Scoop](https://scoop.s The problem? Finding winget package IDs was tedious. While `winget search` works, I wanted something faster - a web interface where I could quickly search, find packages, and copy installation commands. That's how [winget-search](https://github.com/solrevdev/winget-search) was born. -![Winget Search Interface](https://i.imgur.com/YLysmmV.png) - ## The Challenge Winget packages are stored in Microsoft's [winget-pkgs repository](https://github.com/microsoft/winget-pkgs) with over 30,000 YAML manifest files. The repository structure follows a pattern: `manifests/publisher/package_name/version/` with separate files for different locales and installers. @@ -96,17 +95,22 @@ The search interface is pure vanilla JavaScript - no frameworks needed. It loads function showResults(query) { const q = query.trim().toLowerCase(); - let results = packages.filter(pkg => { - const idMatch = pkg.id?.toLowerCase().includes(q); - const nameMatch = pkg.name?.toLowerCase().includes(q); - const descMatch = pkg.description?.toLowerCase().includes(q); - const publisherMatch = pkg.publisher?.toLowerCase().includes(q); - const tagMatch = pkg.tags?.some(tag => - tag && typeof tag === 'string' && tag.toLowerCase().includes(q) - ); - - return idMatch || nameMatch || descMatch || publisherMatch || tagMatch; - }).slice(0, 100); + let results; + if (!q) { + results = packages.slice(0, 50); + } else { + results = packages.filter(pkg => { + const idMatch = pkg.id?.toLowerCase().includes(q); + const nameMatch = pkg.name?.toLowerCase().includes(q); + const descMatch = pkg.description?.toLowerCase().includes(q); + const publisherMatch = pkg.publisher?.toLowerCase().includes(q); + const tagMatch = pkg.tags?.some(tag => + tag && typeof tag === 'string' && tag.toLowerCase().includes(q) + ); + + return idMatch || nameMatch || descMatch || publisherMatch || tagMatch; + }).slice(0, 100); + } // Render results... } @@ -117,8 +121,19 @@ Each search result includes a one-click copy button for the winget install comma ```javascript function copyCommand(button, cmd) { navigator.clipboard.writeText(cmd).then(() => { - button.innerHTML = 'Copied!'; - setTimeout(() => button.innerHTML = 'Copy', 2000); + const originalHtml = button.innerHTML; + button.classList.add('success'); + button.innerHTML = ` + + + + Copied! + `; + + setTimeout(() => { + button.classList.remove('success'); + button.innerHTML = originalHtml; + }, 2000); }); } ``` diff --git a/images/winget-search-cover.svg b/images/winget-search-cover.svg new file mode 100644 index 00000000..7a1d1d2f --- /dev/null +++ b/images/winget-search-cover.svg @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + Winget Package Search + + + Search and install Windows Package Manager packages + + + + Search by package name, ID, or description... + + + + + + + 10,134 packages available + + + Visual Studio Code + Microsoft.VisualStudioCode + v1.95.1 + + Copy + winget install -e --id Microsoft.VisualStudioCode + + + Google Chrome + Google.Chrome + v131.0.6778.86 + + Copy + + + Fast web interface for Windows Package Manager + \ No newline at end of file