From 034db9758ba8e10e672abc9931786c3b7476106d Mon Sep 17 00:00:00 2001 From: Aryanbuha890 Date: Fri, 17 Jul 2026 13:49:56 +0530 Subject: [PATCH 1/2] feat: automate contributors section in README with profile name fetching (#10) --- .github/scripts/update-contributors.js | 223 ++++++++++++++++++++++ .github/workflows/update-contributors.yml | 39 ++++ README.md | 11 ++ 3 files changed, 273 insertions(+) create mode 100644 .github/scripts/update-contributors.js create mode 100644 .github/workflows/update-contributors.yml diff --git a/.github/scripts/update-contributors.js b/.github/scripts/update-contributors.js new file mode 100644 index 0000000..d23d3ee --- /dev/null +++ b/.github/scripts/update-contributors.js @@ -0,0 +1,223 @@ +const fs = require('fs'); +const path = require('path'); + +const README_PATH = path.join(__dirname, '../../README.md'); + +function getContributionType(prPayload) { + const pr = prPayload.pull_request; + const labels = (pr.labels || []).map(l => l.name.toLowerCase()); + + if (labels.some(l => l.includes('frontend') || l.includes('ui') || l.includes('css') || l.includes('design') || l.includes('style'))) { + return '๐ŸŽจ Frontend'; + } + if (labels.some(l => l.includes('design') || l.includes('ux'))) { + return '๐ŸŽจ Design'; + } + if (labels.some(l => l.includes('backend') || l.includes('api') || l.includes('database') || l.includes('db') || l.includes('server'))) { + return '๐Ÿ’ป Code'; + } + if (labels.some(l => l.includes('docs') || l.includes('documentation') || l.includes('readme') || l.includes('wiki'))) { + return '๐Ÿ“– Docs'; + } + if (labels.some(l => l.includes('bug') || l.includes('fix') || l.includes('hotfix') || l.includes('patch'))) { + return '๐Ÿ› Bug fix'; + } + if (labels.some(l => l.includes('test') || l.includes('testing') || l.includes('spec') || l.includes('jest') || l.includes('pytest'))) { + return '๐Ÿงช Tests'; + } + if (labels.some(l => l.includes('performance') || l.includes('speed') || l.includes('perf'))) { + return 'โšก Performance'; + } + if (labels.some(l => l.includes('security') || l.includes('vuln') || l.includes('auth'))) { + return '๐Ÿ”’ Security'; + } + if (labels.some(l => l.includes('infra') || l.includes('ci') || l.includes('cd') || l.includes('workflow') || l.includes('docker') || l.includes('github-actions'))) { + return '๐Ÿš‡ Infrastructure'; + } + if (labels.some(l => l.includes('accessibility') || l.includes('a11y'))) { + return 'โ™ฟ Accessibility'; + } + + // Fallback to title matching + const title = (pr.title || '').toLowerCase(); + if (title.startsWith('feat') || title.includes('frontend') || title.includes('ui')) { + if (title.includes('frontend') || title.includes('ui')) return '๐ŸŽจ Frontend'; + if (title.includes('design')) return '๐ŸŽจ Design'; + if (title.includes('docs') || title.includes('readme')) return '๐Ÿ“– Docs'; + if (title.includes('test')) return '๐Ÿงช Tests'; + if (title.includes('perf')) return 'โšก Performance'; + if (title.includes('security') || title.includes('auth')) return '๐Ÿ”’ Security'; + if (title.includes('infra') || title.includes('workflow') || title.includes('github action')) return '๐Ÿš‡ Infrastructure'; + return '๐Ÿ’ป Code'; + } + if (title.startsWith('fix') || title.includes('bug')) { + return '๐Ÿ› Bug fix'; + } + if (title.startsWith('docs')) { + return '๐Ÿ“– Docs'; + } + if (title.startsWith('test')) { + return '๐Ÿงช Tests'; + } + + return '๐Ÿ’ป Code'; +} + +function parseExistingContributors(sectionContent) { + const contributors = []; + const regex = /\s*\s*]*\/>\s*
\s*([^<]+)<\/b><\/sub>\s*
\s*([^<]+)<\/sub>\s*<\/a>\s*<\/td>/gi; + + let match; + while ((match = regex.exec(sectionContent)) !== null) { + contributors.push({ + username: match[1].trim(), + avatarUrl: match[2].trim(), + name: match[3].trim(), + role: match[4].trim() + }); + } + return contributors; +} + +function generateTableHTML(contributors) { + const columnsPerRow = 5; + let html = '\n
\n\n\n \n'; + + for (let i = 0; i < contributors.length; i += columnsPerRow) { + html += ' \n'; + const chunk = contributors.slice(i, i + columnsPerRow); + for (const c of chunk) { + html += ` \n`; + } + html += ' \n'; + } + + html += ' \n
+ + +
+ ${c.name} +
+ ${c.role} +
+
\n\n
\n'; + return html; +} + +async function main() { + const eventPath = process.env.GITHUB_EVENT_PATH; + if (!eventPath) { + console.error('Error: GITHUB_EVENT_PATH not set.'); + process.exit(1); + } + + let eventPayload; + try { + eventPayload = JSON.parse(fs.readFileSync(eventPath, 'utf8')); + } catch (err) { + console.error('Error reading event payload:', err.message); + process.exit(1); + } + + const pr = eventPayload.pull_request; + if (!pr) { + console.error('Error: Event payload is missing pull_request info.'); + process.exit(1); + } + + // Handle Bot accounts + const username = pr.user.login; + const isBot = pr.user.type === 'Bot' || username.toLowerCase().endsWith('[bot]') || username.toLowerCase() === 'github-actions'; + if (isBot) { + console.log(`Skipping bot contributor: ${username}`); + process.exit(0); + } + + const avatarUrl = `https://github.com/${username}.png`; + let name = username; // Default to username + + // Fetch real profile name from GitHub API (authenticated if GITHUB_TOKEN is available) + const headers = { 'User-Agent': 'learnhub-contributors-automation' }; + if (process.env.GITHUB_TOKEN) { + headers['Authorization'] = `token ${process.env.GITHUB_TOKEN}`; + } + + try { + const response = await fetch(`https://api.github.com/users/${username}`, { headers }); + if (response.ok) { + const userData = await response.json(); + if (userData.name) { + name = userData.name; + console.log(`Fetched display name: "${name}" for user ${username}`); + } + } else { + console.warn(`GitHub API returned status ${response.status} when fetching profile for ${username}`); + } + } catch (err) { + console.warn(`Failed to fetch user profile name from GitHub API, using username instead: ${err.message}`); + } + + const role = getContributionType(eventPayload); + + // Read README + if (!fs.existsSync(README_PATH)) { + console.error(`Error: README.md not found at ${README_PATH}`); + process.exit(1); + } + + let readmeContent = fs.readFileSync(README_PATH, 'utf8'); + + // Locate the contributors section + const startComment = ''; + const endComment = ''; + + const startIndex = readmeContent.indexOf(startComment); + const endIndex = readmeContent.indexOf(endComment); + + if (startIndex === -1 || endIndex === -1 || startIndex >= endIndex) { + console.error('Error: Contributors placeholder comments not found in README.md.'); + process.exit(1); + } + + const sectionContent = readmeContent.substring(startIndex + startComment.length, endIndex); + const contributors = parseExistingContributors(sectionContent); + + // Check for duplicates + const alreadyExists = contributors.some(c => c.username.toLowerCase() === username.toLowerCase()); + if (alreadyExists) { + console.log(`Contributor ${username} already exists in README.md. Skipping.`); + process.exit(0); + } + + // Add new contributor + contributors.push({ + username, + avatarUrl, + name, + role + }); + + console.log(`Adding new contributor: ${username} with role: ${role}`); + + // Generate new HTML table + const newTableHTML = generateTableHTML(contributors); + + // Construct new README content + const updatedReadmeContent = + readmeContent.substring(0, startIndex + startComment.length) + + newTableHTML + + readmeContent.substring(endIndex); + + fs.writeFileSync(README_PATH, updatedReadmeContent, 'utf8'); + console.log('README.md successfully updated.'); +} + +if (require.main === module) { + main(); +} + +module.exports = { + getContributionType, + parseExistingContributors, + generateTableHTML +}; diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml new file mode 100644 index 0000000..0c90b76 --- /dev/null +++ b/.github/workflows/update-contributors.yml @@ -0,0 +1,39 @@ +name: Automate Contributors README Update + +on: + pull_request: + types: [closed] + branches: [main] + +permissions: + contents: write + +jobs: + update-readme: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Update README Contributors + env: + GITHUB_EVENT_PATH: ${{ github.event_path }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node .github/scripts/update-contributors.js + + - name: Commit changes + uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "docs: update contributors in README [skip ci]" + file_pattern: "README.md" + commit_user_name: "github-actions[bot]" + commit_user_email: "github-actions[bot]@users.noreply.github.com" diff --git a/README.md b/README.md index 7909876..9475986 100644 --- a/README.md +++ b/README.md @@ -364,6 +364,7 @@ Contributors who submit PRs will be added to the table below.
+
@@ -378,11 +379,21 @@ Contributors who submit PRs will be added to the table below. ๐ŸŽจ Frontend +
+ + +
+ Aryan Buha +
+ ๐ŸŽจ Frontend +
+
+
From 7278c4df8ade0666ea5117391c3c1ea2f2f08e8e Mon Sep 17 00:00:00 2001 From: Aryanbuha890 Date: Fri, 17 Jul 2026 15:19:20 +0530 Subject: [PATCH 2/2] fix: address reviewer feedback on PR #11 - Remove manual Aryanbuha890 contributor card from README; keep anchors only - Add concurrency group to workflow to prevent race on simultaneous merges - Add unit tests for getContributionType, parseExistingContributors, generateTableHTML --- .github/scripts/update-contributors.test.js | 273 ++++++++++++++++++++ .github/workflows/update-contributors.yml | 4 + README.md | 9 - 3 files changed, 277 insertions(+), 9 deletions(-) create mode 100644 .github/scripts/update-contributors.test.js diff --git a/.github/scripts/update-contributors.test.js b/.github/scripts/update-contributors.test.js new file mode 100644 index 0000000..4e05622 --- /dev/null +++ b/.github/scripts/update-contributors.test.js @@ -0,0 +1,273 @@ +/** + * Tests for update-contributors.js + * + * Run with: node .github/scripts/update-contributors.test.js + * (No extra dependencies needed โ€” uses Node's built-in assert module) + */ + +'use strict'; + +const assert = require('assert'); +const { getContributionType, parseExistingContributors, generateTableHTML } = require('./update-contributors'); + +let passed = 0; +let failed = 0; + +function test(description, fn) { + try { + fn(); + console.log(` โœ… ${description}`); + passed++; + } catch (err) { + console.error(` โŒ ${description}`); + console.error(` ${err.message}`); + failed++; + } +} + +function makePR(labels = [], title = '') { + return { + pull_request: { + labels: labels.map(name => ({ name })), + title + } + }; +} + +// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// getContributionType +// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +console.log('\ngetContributionType'); + +test('returns ๐ŸŽจ Frontend for label "frontend"', () => { + assert.strictEqual(getContributionType(makePR(['frontend'])), '๐ŸŽจ Frontend'); +}); + +test('returns ๐ŸŽจ Frontend for label "ui"', () => { + assert.strictEqual(getContributionType(makePR(['ui'])), '๐ŸŽจ Frontend'); +}); + +test('returns ๐ŸŽจ Frontend for label "css"', () => { + assert.strictEqual(getContributionType(makePR(['css'])), '๐ŸŽจ Frontend'); +}); + +test('returns ๐Ÿ’ป Code for label "backend"', () => { + assert.strictEqual(getContributionType(makePR(['backend'])), '๐Ÿ’ป Code'); +}); + +test('returns ๐Ÿ’ป Code for label "api"', () => { + assert.strictEqual(getContributionType(makePR(['api'])), '๐Ÿ’ป Code'); +}); + +test('returns ๐Ÿ“– Docs for label "docs"', () => { + assert.strictEqual(getContributionType(makePR(['docs'])), '๐Ÿ“– Docs'); +}); + +test('returns ๐Ÿ“– Docs for label "documentation"', () => { + assert.strictEqual(getContributionType(makePR(['documentation'])), '๐Ÿ“– Docs'); +}); + +test('returns ๐Ÿ› Bug fix for label "bug"', () => { + assert.strictEqual(getContributionType(makePR(['bug'])), '๐Ÿ› Bug fix'); +}); + +test('returns ๐Ÿ› Bug fix for label "fix"', () => { + assert.strictEqual(getContributionType(makePR(['fix'])), '๐Ÿ› Bug fix'); +}); + +test('returns ๐Ÿงช Tests for label "test"', () => { + assert.strictEqual(getContributionType(makePR(['test'])), '๐Ÿงช Tests'); +}); + +test('returns ๐Ÿงช Tests for label "jest"', () => { + assert.strictEqual(getContributionType(makePR(['jest'])), '๐Ÿงช Tests'); +}); + +test('returns โšก Performance for label "performance"', () => { + assert.strictEqual(getContributionType(makePR(['performance'])), 'โšก Performance'); +}); + +test('returns ๐Ÿ”’ Security for label "security"', () => { + assert.strictEqual(getContributionType(makePR(['security'])), '๐Ÿ”’ Security'); +}); + +test('returns ๐Ÿš‡ Infrastructure for label "ci"', () => { + assert.strictEqual(getContributionType(makePR(['ci'])), '๐Ÿš‡ Infrastructure'); +}); + +test('returns ๐Ÿš‡ Infrastructure for label "docker"', () => { + assert.strictEqual(getContributionType(makePR(['docker'])), '๐Ÿš‡ Infrastructure'); +}); + +test('returns โ™ฟ Accessibility for label "a11y"', () => { + assert.strictEqual(getContributionType(makePR(['a11y'])), 'โ™ฟ Accessibility'); +}); + +// Label takes priority over title +test('label takes priority over title prefix', () => { + assert.strictEqual(getContributionType(makePR(['docs'], 'feat: add new thing')), '๐Ÿ“– Docs'); +}); + +// Title fallback tests (no labels) +test('title starting with "fix" returns ๐Ÿ› Bug fix', () => { + assert.strictEqual(getContributionType(makePR([], 'fix: correct typo')), '๐Ÿ› Bug fix'); +}); + +test('title starting with "docs" returns ๐Ÿ“– Docs', () => { + assert.strictEqual(getContributionType(makePR([], 'docs: update README')), '๐Ÿ“– Docs'); +}); + +test('title starting with "test" returns ๐Ÿงช Tests', () => { + assert.strictEqual(getContributionType(makePR([], 'test: add unit tests')), '๐Ÿงช Tests'); +}); + +test('title with "ui" keyword returns ๐ŸŽจ Frontend', () => { + assert.strictEqual(getContributionType(makePR([], 'feat: improve ui layout')), '๐ŸŽจ Frontend'); +}); + +test('generic feat title defaults to ๐Ÿ’ป Code', () => { + assert.strictEqual(getContributionType(makePR([], 'feat: some backend feature')), '๐Ÿ’ป Code'); +}); + +test('returns ๐Ÿ’ป Code as ultimate fallback (no labels, unknown title)', () => { + assert.strictEqual(getContributionType(makePR([], 'chore: misc update')), '๐Ÿ’ป Code'); +}); + +// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// parseExistingContributors +// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +console.log('\nparseExistingContributors'); + +const SINGLE_ENTRY_HTML = ` + +
+ +
+ Alice Smith +
+ ๐Ÿ’ป Code +
+`; + +const MULTI_ENTRY_HTML = ` + + + +
+ Alice Smith +
+ ๐Ÿ’ป Code +
+ + + + +
+ Bob Jones +
+ ๐Ÿ“– Docs +
+`; + +test('returns empty array for empty section', () => { + const result = parseExistingContributors(''); + assert.deepStrictEqual(result, []); +}); + +test('parses a single contributor entry correctly', () => { + const result = parseExistingContributors(SINGLE_ENTRY_HTML); + assert.strictEqual(result.length, 1); + assert.strictEqual(result[0].username, 'alice'); + assert.strictEqual(result[0].avatarUrl, 'https://github.com/alice.png'); + assert.strictEqual(result[0].name, 'Alice Smith'); + assert.strictEqual(result[0].role, '๐Ÿ’ป Code'); +}); + +test('parses multiple contributor entries', () => { + const result = parseExistingContributors(MULTI_ENTRY_HTML); + assert.strictEqual(result.length, 2); + assert.strictEqual(result[0].username, 'alice'); + assert.strictEqual(result[1].username, 'bob'); + assert.strictEqual(result[1].role, '๐Ÿ“– Docs'); +}); + +test('is case-insensitive for username matching (regex)', () => { + // The regex uses the gi flag; verify duplicates found regardless of case + const result = parseExistingContributors(SINGLE_ENTRY_HTML); + const found = result.some(c => c.username.toLowerCase() === 'alice'); + assert.ok(found); +}); + +// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// generateTableHTML +// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +console.log('\ngenerateTableHTML'); + +const ONE_CONTRIBUTOR = [ + { username: 'alice', avatarUrl: 'https://github.com/alice.png', name: 'Alice Smith', role: '๐Ÿ’ป Code' } +]; + +const SIX_CONTRIBUTORS = Array.from({ length: 6 }, (_, i) => ({ + username: `user${i}`, + avatarUrl: `https://github.com/user${i}.png`, + name: `User ${i}`, + role: '๐Ÿ’ป Code' +})); + +test('returns a string', () => { + assert.strictEqual(typeof generateTableHTML(ONE_CONTRIBUTOR), 'string'); +}); + +test('contains and
tags', () => { + const html = generateTableHTML(ONE_CONTRIBUTOR); + assert.ok(html.includes(''), 'Missing
'); + assert.ok(html.includes('
'), 'Missing '); +}); + +test('contains correct github profile link for contributor', () => { + const html = generateTableHTML(ONE_CONTRIBUTOR); + assert.ok(html.includes('https://github.com/alice')); +}); + +test('contains contributor avatar img', () => { + const html = generateTableHTML(ONE_CONTRIBUTOR); + assert.ok(html.includes('https://github.com/alice.png')); +}); + +test('contains contributor display name', () => { + const html = generateTableHTML(ONE_CONTRIBUTOR); + assert.ok(html.includes('Alice Smith')); +}); + +test('contains contributor role', () => { + const html = generateTableHTML(ONE_CONTRIBUTOR); + assert.ok(html.includes('๐Ÿ’ป Code')); +}); + +test('wraps into multiple rows for 6 contributors (max 5 per row)', () => { + const html = generateTableHTML(SIX_CONTRIBUTORS); + // Should have at least 2 tags + const trCount = (html.match(//g) || []).length; + assert.ok(trCount >= 2, `Expected โ‰ฅ2 tags, got ${trCount}`); +}); + +test('first row has exactly 5 entries for 6 contributors', () => { + const html = generateTableHTML(SIX_CONTRIBUTORS); + // Count td elements โ€” 6 total across 2 rows + const tdCount = (html.match(//g) || []).length; + assert.strictEqual(tdCount, 6, `Expected 6 entries, got ${tdCount}`); +}); + +test('empty contributors array returns table with no rows', () => { + const html = generateTableHTML([]); + assert.ok(!html.includes(''), 'Should have no for empty input'); +}); + +// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +// Summary +// โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +console.log(`\n${'โ”€'.repeat(50)}`); +console.log(`Results: ${passed} passed, ${failed} failed`); +if (failed > 0) { + process.exit(1); +} diff --git a/.github/workflows/update-contributors.yml b/.github/workflows/update-contributors.yml index 0c90b76..a3d431f 100644 --- a/.github/workflows/update-contributors.yml +++ b/.github/workflows/update-contributors.yml @@ -8,6 +8,10 @@ on: permissions: contents: write +concurrency: + group: update-contributors + cancel-in-progress: false + jobs: update-readme: if: github.event.pull_request.merged == true diff --git a/README.md b/README.md index 9475986..2476174 100644 --- a/README.md +++ b/README.md @@ -379,15 +379,6 @@ Contributors who submit PRs will be added to the table below. ๐ŸŽจ Frontend - - - -
- Aryan Buha -
- ๐ŸŽจ Frontend -
-