VSCode Marketplace – Direct VSIX Download

Adds a “Download VSIX” button to VS Code Marketplace pages, keeps it there, and saves the file with a proper name.

K instalaci tototo skriptu si budete muset nainstalovat rozšíření jako Tampermonkey, Greasemonkey nebo Violentmonkey.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Violentmonkey.

K instalaci tohoto skriptu si budete muset nainstalovat rozšíření jako Tampermonkey nebo Userscripts.

You will need to install an extension such as Tampermonkey to install this script.

K instalaci tohoto skriptu si budete muset nainstalovat manažer uživatelských skriptů.

(Už mám manažer uživatelských skriptů, nechte mě ho nainstalovat!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(Už mám manažer uživatelských stylů, nechte mě ho nainstalovat!)

// ==UserScript==
// @name         VSCode Marketplace – Direct VSIX Download
// @namespace    https://github.com/sreyemnayr
// @version      0.2.1
// @description  Adds a “Download VSIX” button to VS Code Marketplace pages, keeps it there, and saves the file with a proper name.
// @author       Ryan Meyers (sreyemnayr)
// @match        https://marketplace.visualstudio.com/items*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

async function injectDownloadButton () {
  /*—-1-— make sure we’re on an extension-detail page —*/
  const params   = new URLSearchParams(location.search);
  const itemName = params.get('itemName');                       // e.g. Balastrong.close-tabs
  if (!itemName || !itemName.includes('.')) return;

  /* If the button is already in the DOM, nothing to do */
  if (document.getElementById('vsix-download-container')) return;

  const [publisher, extension] = itemName.split('.');

  /*—-2-— pull metadata for latest VSIX —*/
  const apiUrl = `https://marketplace.visualstudio.com/_apis/public/gallery/vscode/${publisher}/${extension}/latest`;
  let latest, version, vsixUrl;
  try {
    const json = await fetch(apiUrl).then(r => r.json());
    latest  = json?.versions?.[0];
    version = latest?.version ?? '0.0.0';
    vsixUrl = latest?.files?.find(f => f.assetType === 'Microsoft.VisualStudio.Services.VSIXPackage')?.source;
    if (!vsixUrl) return;
  } catch (e) {
    console.error('[VSIX] metadata fetch failed:', e);
    return;
  }

  /*—-3-— find the Install button so we can clone its look —*/
  const install   = document.querySelector('.install');
  const container = install?.closest('.ux-oneclick-install-button-container')?.parentNode;
  if (!install || !container) return;

  const dlBtn = install.cloneNode(true);
  dlBtn.id = 'vsix-download-button';
  dlBtn.querySelector('*').textContent = 'Download VSIX';
  dlBtn.removeAttribute('href');               // we handle click ourselves
  dlBtn.style.pointerEvents = 'auto';

  dlBtn.addEventListener('click', async ev => {
    ev.preventDefault();
    const label = dlBtn.querySelector('*');
    const originalText = label.textContent;
    label.textContent = 'Fetching…';

    try {
      const buffer = await fetch(vsixUrl).then(r => r.arrayBuffer());
      const blob   = new Blob([buffer], { type: 'application/octet-stream' });
      const url    = URL.createObjectURL(blob);

      const tmp = document.createElement('a');
      tmp.href = url;
      tmp.download = `${publisher}.${extension}.${version}.vsix`;
      document.body.appendChild(tmp);
      tmp.click();
      tmp.remove();
      URL.revokeObjectURL(url);

      label.textContent = 'Downloaded ✔';
      setTimeout(() => (label.textContent = originalText), 2500);
    } catch (err) {
      console.error('[VSIX] download failed:', err);
      label.textContent = 'Error – try again';
      setTimeout(() => (label.textContent = originalText), 3000);
    }
  });

  const wrap = document.createElement('div');
  wrap.id = 'vsix-download-container';
  wrap.appendChild(dlBtn);
  container.appendChild(wrap);

  console.log('[VSIX] Download button added →', vsixUrl);
}

/*—-4-— keep the button present even if the page rewrites the DOM —*/
setInterval(injectDownloadButton, 1000);       // every second
window.addEventListener('popstate', injectDownloadButton); // SPA navigation support