Commit 18545c6

Anton Medvedev <anton@medv.io>
2023-10-09 17:53:43
Add fix-commits
1 parent 0204e45
src/all-badges/fix-commit/fix-2.png
Binary file
src/all-badges/fix-commit/fix-3.png
Binary file
src/all-badges/fix-commit/fix-4.png
Binary file
src/all-badges/fix-commit/fix-5.png
Binary file
src/all-badges/fix-commit/fix-6+.png
Binary file
src/all-badges/fix-commit/fix-6.png
Binary file
src/all-badges/fix-commit/fix-commit.ts
@@ -0,0 +1,63 @@
+import { BadgePresenter, Grant, Present } from '../../badges.js'
+import { Commit } from '../../collect/collect.js'
+
+export default new (class implements BadgePresenter {
+  url = new URL(import.meta.url)
+  badges = [
+    'fix-2',
+    'fix-3',
+    'fix-4',
+    'fix-5',
+    'fix-6',
+    'fix-6+', // For more than 6
+  ] as const
+  present: Present = (data, grant) => {
+    for (const repo of data.repos) {
+      let sequentialFixes = 0
+      let previousCommitDate = null
+      let evidence: Commit[] = []
+
+      for (const commit of repo.commits) {
+        const currentCommitDate = new Date(commit.committedDate)
+
+        // If the commit message contains "fix" and is within a 15-minute span of the previous commit
+        if (
+          commit.message.includes('fix') &&
+          (previousCommitDate === null ||
+            currentCommitDate.getTime() - previousCommitDate.getTime() <=
+              15 * 60 * 1000)
+        ) {
+          sequentialFixes++
+          evidence.push(commit)
+        } else {
+          this.grantBadge(sequentialFixes, grant, evidence)
+          evidence = commit.message.includes('fix') ? [commit] : []
+          sequentialFixes = evidence.length
+        }
+
+        previousCommitDate = currentCommitDate
+      }
+
+      // Check for any remaining sequences after exiting the loop
+      if (sequentialFixes > 0) {
+        this.grantBadge(sequentialFixes, grant, evidence)
+      }
+    }
+  }
+
+  grantBadge(count: number, grant: Grant, evidence: Commit[]) {
+    let description = `Granted for making ${count} sequential fixes.`
+
+    if (count === 2) grant('fix-2', description).evidenceCommits(...evidence)
+    else if (count === 3)
+      grant('fix-3', description).evidenceCommits(...evidence)
+    else if (count === 4)
+      grant('fix-4', description).evidenceCommits(...evidence)
+    else if (count === 5)
+      grant('fix-5', description).evidenceCommits(...evidence)
+    else if (count === 6)
+      grant('fix-6', description).evidenceCommits(...evidence)
+    else if (count > 6)
+      grant('fix-6+', description).evidenceCommits(...evidence)
+  }
+})()
src/all-badges/star-gazer/star-gazer.ts
@@ -4,7 +4,7 @@ export default new (class implements BadgePresenter {
   url = new URL(import.meta.url)
   badges = ['star-gazer'] as const
   present: Present = (data, grant) => {
-    if (data.user.starredRepositories.totalCount >= 1000) {
+    if (data.user.starredRepositories?.totalCount >= 1000) {
       grant('star-gazer', "I'm a star gazer!").evidence(
         "I've starred over 1000 repositories!",
       )
src/all-badges/index.ts
@@ -9,4 +9,5 @@ export const allBadges = [
   await import('./mass-delete-commit/mass-delete-commit.js'),
   await import('./revert-revert-commit/revert-revert-commit.js'),
   await import('./my-badges-contributor/my-badges-contributor.js'),
+  await import('./fix-commit/fix-commit.js'),
 ] as const
src/badges.ts
@@ -1,9 +1,15 @@
 import { allBadges } from './all-badges/index.js'
 import { Data, Commit, Pull } from './collect/collect.js'
-import { linkCommit, linkPull } from './utils.js'
+import { expectType, linkCommit, linkPull } from './utils.js'
 import { fileURLToPath } from 'url'
 import * as path from 'path'
 
+for (const {
+  default: { badges },
+} of allBadges) {
+  expectType<readonly [string, ...string[]]>(badges)
+}
+
 export type ID = (typeof allBadges)[number]['default']['badges'][number]
 
 export interface BadgePresenter {
@@ -12,10 +18,9 @@ export interface BadgePresenter {
   present: Present
 }
 
-export type Present = (
-  data: Data,
-  grant: ReturnType<typeof badgeCollection>,
-) => void
+export type Grant = ReturnType<typeof badgeCollection>
+
+export type Present = (data: Data, grant: Grant) => void
 
 export type Badge = {
   id: ID
src/utils.ts
@@ -7,7 +7,7 @@ export function linkCommit(commit: Commit): string {
 }
 
 export function linkPull(pull: Pull): string {
-  return `<a href=https://github.com/${pull.repository.owner.login}/${pull.repository.name}/pull/${pull.number}>#${pull.number}</a>`
+  return `<a href="https://github.com/${pull.repository.owner.login}/${pull.repository.name}/pull/${pull.number}">#${pull.number}</a>`
 }
 
 export function quoteAttr(s: string) {
@@ -20,3 +20,5 @@ export function quoteAttr(s: string) {
     .replace(/\r\n/g, '&#13;')
     .replace(/[\r\n]/g, '&#13;')
 }
+
+export const expectType = <T>(expression: T) => void 0