VS Marketplace: Download VSIX

Adds a "Download VSIX" button next to Install, copying its style exactly.

// ==UserScript==
// @name         VS Marketplace: Download VSIX
// @namespace    https://brandonhenness.dev/tm/vsix-download
// @version      1.8.1
// @description  Adds a "Download VSIX" button next to Install, copying its style exactly.
// @match        https://marketplace.visualstudio.com/items*
// @run-at       document-idle
// @grant        none
// @license      GPL-3.0-only; https://www.gnu.org/licenses/gpl-3.0.txt
// @author       Brandon Henness
// ==/UserScript==

(function () {
  "use strict";

  const BTN_ID = "bh-vsix-dl";

  const getItem = () => {
    try {
      const u = new URL(location.href);
      const q = u.searchParams.get("itemName");
      if (q && q.includes(".")) return q;
      const m = u.pathname.match(/items\/([^/?#]+)/i);
      if (m && m[1] && m[1].includes(".")) return m[1];
    } catch {}
    return null;
  };

  function findInstallButton() {
    return document.querySelector(".ux-oneclick-install-button-container .install");
  }

  function createDownloadButton() {
    const installBtn = findInstallButton();
    if (!installBtn) return null;

    // Clone Install button for identical style
    const clone = installBtn.cloneNode(true);
    clone.id = BTN_ID;

    // Add spacing so the buttons are not touching
    clone.style.marginLeft = "8px";

    // Change label
    const labelDiv = clone.querySelector(".ms-Button-label");
    if (labelDiv) labelDiv.textContent = "Download VSIX";

    // Wire up latest VSIX URL
    const key = getItem();
    if (key) {
      const [publisher, extension] = key.split(".");
      clone.href = `https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher}/vsextensions/${extension}/latest/vspackage`;
      clone.setAttribute("download", `${publisher}.${extension}_latest.vsix`);
      clone.removeAttribute("aria-disabled");
    }

    return clone;
  }

  function ensureButton() {
    if (document.getElementById(BTN_ID)) return;

    const installBtn = findInstallButton();
    if (installBtn && installBtn.parentElement) {
      const dlBtn = createDownloadButton();
      if (dlBtn) installBtn.parentElement.appendChild(dlBtn);
    }
  }

  // Keep the button present across SPA re-renders
  const observer = new MutationObserver(() => ensureButton());
  observer.observe(document.documentElement, { childList: true, subtree: true });

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", ensureButton);
  } else {
    ensureButton();
  }
})();