diff --git a/README.md b/README.md
index d30c83a..41a6c18 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@ Want support for more sites? Please [submit an issue](https://github.com/jonfrie
| | Repository |
| | User/Organization |
| | Release |
+| | Action Runs |
| **Instagram** | |
| | Profile |
| **LinkedIn** | |
diff --git a/src/content/config.e2e.js b/src/content/config.e2e.js
index 379ef7e..bbef4fe 100644
--- a/src/content/config.e2e.js
+++ b/src/content/config.e2e.js
@@ -27,7 +27,13 @@ async function testPageConfig(page, url, config) {
// Verify that all expected properties are present and non-empty
for (const key in info) {
- expect(info[key]).toBeTruthy()
+ expect(info[key]).not.toBe(undefined, `Expected info[${key}] to be defined, but it was undefined`)
+ expect(info[key]).not.toBe(null, `Expected info[${key}] to be non-null, but it was null`)
+ if (typeof info[key] === 'string') {
+ expect(info[key].trim()).not.toBe('', `Expected info[${key}] to be non-empty string, but it was: "${info[key]}"`)
+ } else {
+ expect(info[key]).toBeTruthy(`Expected info[${key}] to be truthy, but it was: ${JSON.stringify(info[key])}`)
+ }
}
// Test markdown and plaintext building functions
@@ -54,6 +60,7 @@ for (const [siteName, siteConfig] of Object.entries(siteConfigs)) {
user: 'https://github.com/microsoft',
release: 'https://github.com/microsoft/playwright/releases/tag/v1.32.3',
commit: 'https://github.com/golang/go/commit/96d8ff00c2d6a88384863a656fb5e53716b614d3',
+ action_run: 'https://github.com/jonfriesen/quickcite/actions/runs/10446232111',
},
instagram: {
profile: 'https://www.instagram.com/jonfriesen/',
diff --git a/src/content/config.js b/src/content/config.js
index f0dd70c..22a75bb 100644
--- a/src/content/config.js
+++ b/src/content/config.js
@@ -148,6 +148,68 @@ const siteConfigs = {
return `(${info.hash.slice(0, 7)}) -${info.title}\nBy ${info.author} on ${formattedDate}\n${url}`
},
},
+ action_run: {
+ urlPattern: /^https:\/\/github\.com\/[^\/]+\/[^\/]+\/actions\/runs\/\d+$/,
+ buttonId: 'gh-action-run-copy-button',
+ getInfo: () => {
+ const getStatus = () => {
+ const statusLabel = Array.from(document.querySelectorAll('span.mb-1.d-block.text-small.color-fg-muted')).find((el) => el.textContent.trim() === 'Status')
+
+ if (statusLabel) {
+ // If found, get the next sibling span which contains the status value
+ const statusValue = statusLabel.nextElementSibling
+ if (statusValue && statusValue.classList.contains('h4') && statusValue.classList.contains('color-fg-default')) {
+ return statusValue.textContent.trim()
+ }
+ }
+ return null
+ }
+ const getStatusEmoji = (status) => {
+ const statusMap = {
+ Queued: 'โณ',
+ 'In progress': '๐',
+ Success: 'โ
',
+ Failure: 'โ',
+ Cancelled: '๐ซ',
+ Skipped: 'โญ๏ธ',
+ }
+ return statusMap[status] || ''
+ }
+ const extractRepoInfo = (str) => {
+ const parts = str.split('ยท').map((part) => part.trim())
+
+ if (parts.length >= 2) {
+ const repoInfo = parts[1].split('/')
+ if (repoInfo.length >= 2) {
+ const [name, hash] = repoInfo[1].split('@')
+ return {
+ owner: repoInfo[0],
+ name: name,
+ hash: hash || null, // In case there's no hash
+ }
+ }
+ }
+
+ return null
+ }
+
+ // Test the function
+ const repoInfo = extractRepoInfo(document.title)
+ const workflowName = document.querySelector('h1.PageHeader-title span.markdown-title').textContent.trim()
+ const runNumber = document.querySelector('h1.PageHeader-title span.color-fg-muted').textContent.trim()
+ const runStatus = getStatus()
+ const repoOwner = repoInfo.owner
+ const repoName = repoInfo.name
+ const statusEmoji = getStatusEmoji(runStatus)
+ return { workflowName, runStatus, runNumber, repoName, repoOwner, statusEmoji }
+ },
+ buildMarkdown: (info, url) => {
+ return `[${info.repoOwner}/${info.repoName}: ${info.workflowName} ${info.runNumber} ${info.statusEmoji} (${info.runStatus})](${url})`
+ },
+ buildPlaintext: (info, url) => {
+ return `${info.repoOwner}/${info.repoName}: ${info.workflowName} ${info.runNumber} ${info.statusEmoji} (${info.runStatus}) - ${url}`
+ },
+ },
},
},
instagram: {
diff --git a/src/content/github.test.js b/src/content/github.test.js
index 43cce32..df4fef3 100644
--- a/src/content/github.test.js
+++ b/src/content/github.test.js
@@ -10,6 +10,11 @@ describe('GitHub URL pattern tests', () => {
{ page: 'release', url: 'https://github.com/user/repo/releases/tag/v1.0.0', shouldMatch: true },
{ page: 'commit', url: 'https://github.com/user/repo/commit/1234567890123456789012345678901234567890', shouldMatch: true },
{ page: 'pr', url: 'https://github.com/user/repo', shouldMatch: false },
+ { page: 'action_run', url: 'https://github.com/jonfriesen/quickcite/actions/runs/10476888027', shouldMatch: true },
+ { page: 'action_run', url: 'https://github.com/octocat/Hello-World/actions/runs/12345678', shouldMatch: true },
+ { page: 'action_run', url: 'https://github.com/octocat/Hello-World/actions', shouldMatch: false },
+ { page: 'action_run', url: 'https://github.com/octocat/Hello-World/actions/runs/abcdefgh', shouldMatch: false },
+ { page: 'action_run', url: 'https://github.com/octocat/Hello-World/actions/runs/abcdefgh/job/12345678', shouldMatch: false },
]
test.each(testCases)('$page: $url should ${shouldMatch ? "match" : "not match"}', ({ page, url, shouldMatch }) => {
@@ -23,6 +28,18 @@ describe('GitHub URL pattern tests', () => {
})
describe('GitHub markdown generator tests', () => {
+ const mockGetStatusEmoji = (status) => {
+ const statusMap = {
+ Queued: 'โณ',
+ 'In progress': '๐',
+ Success: 'โ
',
+ Failure: 'โ',
+ Cancelled: '๐ซ',
+ Skipped: 'โญ๏ธ',
+ }
+ return statusMap[status] || ''
+ }
+
const markdownTestCases = [
{
page: 'pr',
@@ -66,6 +83,24 @@ describe('GitHub markdown generator tests', () => {
url: 'https://github.com/user/repo/commit/1234567890abcdef',
expected: '[(1234567) - Update README.md](https://github.com/user/repo/commit/1234567890abcdef)\nBy John Doe on ' + new Date('2023-05-01T00:00:00Z').toLocaleString(),
},
+ {
+ page: 'action_run',
+ info: { repoOwner: 'jonfriesen', repoName: 'quickcite', workflowName: 'CI', runNumber: '#1234', runStatus: 'Success', statusEmoji: 'โ
' },
+ url: 'https://github.com/jonfriesen/quickcite/actions/runs/10476888027',
+ expected: '[jonfriesen/quickcite: CI #1234 โ
(Success)](https://github.com/jonfriesen/quickcite/actions/runs/10476888027)',
+ },
+ {
+ page: 'action_run',
+ info: { repoOwner: 'octocat', repoName: 'Hello-World', workflowName: 'Build', runNumber: '#5678', runStatus: 'Failure', statusEmoji: 'โ' },
+ url: 'https://github.com/octocat/Hello-World/actions/runs/12345678',
+ expected: '[octocat/Hello-World: Build #5678 โ (Failure)](https://github.com/octocat/Hello-World/actions/runs/12345678)',
+ },
+ {
+ page: 'action_run',
+ info: { repoOwner: 'test', repoName: 'repo', workflowName: 'Deploy', runNumber: '#9012', runStatus: 'In progress', statusEmoji: '๐' },
+ url: 'https://github.com/test/repo/actions/runs/90123456',
+ expected: '[test/repo: Deploy #9012 ๐ (In progress)](https://github.com/test/repo/actions/runs/90123456)',
+ },
]
test.each(markdownTestCases)('$page: should generate correct markdown', ({ page, info, url, expected }) => {
diff --git a/web/src/components/Intro.jsx b/web/src/components/Intro.jsx
index a51e598..1a7daf6 100644
--- a/web/src/components/Intro.jsx
+++ b/web/src/components/Intro.jsx
@@ -123,7 +123,7 @@ export function Intro() {