Skip to content

Instantly share code, notes, and snippets.

@gartnera
Last active April 8, 2026 16:21
Show Gist options
  • Select an option

  • Save gartnera/8fef1ed5707d2ab5901d688b3d6a8dc6 to your computer and use it in GitHub Desktop.

Select an option

Save gartnera/8fef1ed5707d2ab5901d688b3d6a8dc6 to your computer and use it in GitHub Desktop.
Allow copying github artifacts URLs. Show -profile.gz as a clickable link to perfetto UI.
// ==UserScript==
// @name GitHub Actions Artifact Presigned URL Copier
// @namespace https://agartner.com/
// @version 1.5
// @description Copy presigned URLs for GitHub Actions artifacts, open -profile.gz in Perfetto
// @match https://github.com/*/actions/runs/*
// @grant GM_setClipboard
// @grant GM_xmlhttpRequest
// @connect productionresultssa15.blob.core.windows.net
// @connect blob.core.windows.net
// ==/UserScript==
(function () {
'use strict';
function getGithubArtifactUrl(repoPath, runId, artifactId) {
return 'https://github.com/' + repoPath + '/actions/runs/' + runId + '/artifacts/' + artifactId;
}
function getPresignedUrl(repoPath, runId, artifactId) {
return new Promise(function (resolve, reject) {
GM_xmlhttpRequest({
method: 'GET',
url: 'https://github.com/' + repoPath + '/actions/runs/' + runId + '/artifacts/' + artifactId,
redirect: 'follow',
onload: function (res) {
console.log('[presigned] status:', res.status, 'finalUrl:', res.finalUrl);
if (res.finalUrl && res.finalUrl.indexOf('blob.core.windows.net') !== -1) {
resolve(res.finalUrl);
} else {
reject(new Error('Unexpected finalUrl: ' + res.finalUrl));
}
},
onerror: function (err) {
reject(new Error('GM_xmlhttpRequest error: ' + JSON.stringify(err)));
},
});
});
}
function setButtonState(btn, state, label) {
var original = { html: btn.innerHTML, color: btn.style.color };
btn.textContent = label;
btn.style.color = state === 'success'
? 'var(--fgColor-success, #3fb950)'
: 'var(--fgColor-danger, #f85149)';
setTimeout(function () {
btn.innerHTML = original.html;
btn.style.color = original.color;
btn.disabled = false;
}, 2000);
}
function makeBtn(title, svgPath) {
var btn = document.createElement('button');
btn.type = 'button';
btn.title = title;
btn.style.cssText = 'background:none;border:none;cursor:pointer;padding:4px;color:var(--fgColor-muted,#848d97);display:inline-flex;align-items:center;vertical-align:middle;';
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('aria-hidden', 'true');
svg.setAttribute('height', '16');
svg.setAttribute('width', '16');
svg.setAttribute('viewBox', '0 0 16 16');
svg.setAttribute('fill', 'currentColor');
var path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d', svgPath);
svg.appendChild(path);
btn.appendChild(svg);
return btn;
}
var COPY_PATH = 'M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25ZM5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z';
var PERFETTO_PATH = 'M9.504.43a1.516 1.516 0 0 1 2.437 1.713L10.415 5.5h2.123c1.57 0 2.346 1.909 1.22 3.004l-7.34 7.142a1.249 1.249 0 0 1-.871.354h-.302a1.25 1.25 0 0 1-1.157-1.723L5.633 10.5H3.462c-1.57 0-2.346-1.909-1.22-3.004L9.504.43Zm.094 2.497L4.557 8.485a.25.25 0 0 0 .178.427h2.668a.75.75 0 0 1 .697 1.023l-1.2 3.015 5.047-4.91a.25.25 0 0 0-.178-.427h-2.497a.75.75 0 0 1-.626-1.166l1.952-3.06Z';
function createCopyButton(repoPath, runId, artifactId) {
var btn = makeBtn('Copy presigned URL', COPY_PATH);
btn.classList.add('presigned-copy-btn');
btn.addEventListener('click', async function () {
btn.disabled = true;
try {
var url = await getPresignedUrl(repoPath, runId, artifactId);
GM_setClipboard(url);
setButtonState(btn, 'success', '✓');
} catch (err) {
console.error('presigned-url-copier:', err);
setButtonState(btn, 'error', '✗');
}
});
return btn;
}
function createPerfettoButton(repoPath, runId, artifactId) {
var btn = makeBtn('Open in Perfetto', PERFETTO_PATH);
btn.classList.add('presigned-perfetto-btn');
btn.addEventListener('click', async function () {
btn.disabled = true;
try {
var url = await getPresignedUrl(repoPath, runId, artifactId);
window.open('https://perfetto.agartner.com/#!/?url=' + encodeURIComponent(url), '_blank');
setButtonState(btn, 'success', '✓');
} catch (err) {
console.error('presigned-url-copier:', err);
setButtonState(btn, 'error', '✗');
}
});
return btn;
}
function injectButtons() {
var urlMatch = window.location.pathname.match(/^\/([^/]+\/[^/]+)\/actions\/runs\/(\d+)/);
if (!urlMatch) return;
var repoPath = urlMatch[1];
var runId = urlMatch[2];
document.querySelectorAll('tr[data-artifact-id]').forEach(function (row) {
var artifactId = row.getAttribute('data-artifact-id');
if (!artifactId || row.querySelector('.presigned-copy-btn')) return;
var downloadLink = row.querySelector('a[data-test-selector="download-artifact-button"]');
if (!downloadLink) return;
var btnContainer = downloadLink.closest('.d-flex');
if (!btnContainer) return;
var nameAnchor = row.querySelector('a[aria-label^="Download"]');
var artifactName = nameAnchor ? nameAnchor.getAttribute('aria-label') : '';
var isProfile = /-profile\.gz/.test(artifactName);
var deleteForm = btnContainer.querySelector('form');
var copyBtn = createCopyButton(repoPath, runId, artifactId);
if (deleteForm) {
btnContainer.insertBefore(copyBtn, deleteForm);
if (isProfile) {
btnContainer.insertBefore(createPerfettoButton(repoPath, runId, artifactId), deleteForm);
}
} else {
btnContainer.appendChild(copyBtn);
if (isProfile) {
btnContainer.appendChild(createPerfettoButton(repoPath, runId, artifactId));
}
}
});
}
injectButtons();
new MutationObserver(injectButtons).observe(document.body, { childList: true, subtree: true });
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment