Commit 6114a1e

Anton Medvedev <anton@medv.io>
2024-07-05 22:50:03
Refactor types (#66)
1 parent 706551c
Changed files (113)
badges
abc-commit
bad-words
cafe-commit
chore-commit
cosmetic-commit
covid-19
dead-commit
delorean
emoji-only-commit
favorite-word
fix-commit
github-anniversary
mass-delete-commit
my-badges-contributor
old-issue
polite-coder
pr-collaboration
public-keys
revert-revert-commit
spooky-commit
star-gazer
stars
the-ultimate-question
this-is-fine
time-of-commit
yeti
scripts
src
test
badges/abc-commit/a-commit.png
Binary file
badges/abc-commit/ab-commit.png
Binary file
badges/abc-commit/abc-commit.png
Binary file
src/all-badges/abc-commit/abc-commit.ts โ†’ badges/abc-commit/abc-commit.ts
@@ -1,19 +1,18 @@
-import { BadgePresenter, ID, Present } from '../../badges.js'
-import { Commit, Repo } from '../../collect/types.js'
+import { Commit, define, Repo } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  tiers = true
-  badges = [
+export default define({
+  url: import.meta.url,
+  tiers: true,
+  badges: [
     'a-commit',
     'ab-commit',
     'abc-commit',
     'abcd-commit',
     'abcde-commit',
     'abcdef-commit',
-  ] as const
-  present: Present = (data, grant) => {
-    const types: [string, ID][] = [
+  ] as const,
+  present(data, grant) {
+    const types: [string, (typeof this.badges)[number]][] = [
       ['abcdef', 'abcdef-commit'],
       ['abcde', 'abcde-commit'],
       ['abcd', 'abcd-commit'],
@@ -43,8 +42,8 @@ export default new (class implements BadgePresenter {
         fn()
       }
     }
-  }
-})()
+  },
+})
 
 function link(re: RegExp, repo: Repo, commit: Commit) {
   const sha = commit.sha.replace(re, '<strong>$1</strong>')
badges/abc-commit/abcd-commit.png
Binary file
badges/abc-commit/abcde-commit.png
Binary file
badges/abc-commit/abcdef-commit.png
Binary file
badges/bad-words/bad-words.png
Binary file
src/all-badges/bad-words/bad-words.ts โ†’ badges/bad-words/bad-words.ts
@@ -1,11 +1,9 @@
-import { Present, BadgePresenter } from '../../badges.js'
-import { linkCommit } from '../../utils.js'
-import { Commit, Repo } from '../../collect/types.js'
+import { Commit, define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['bad-words'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['bad-words'] as const,
+  present(data, grant) {
     const commits: Commit[] = []
 
     for (const repo of data.repos) {
@@ -22,5 +20,5 @@ export default new (class implements BadgePresenter {
         'I used a word "fuck" in my commit message.',
       ).evidenceCommitsWithMessage(...commits)
     }
-  }
-})()
+  },
+})
badges/cafe-commit/cafe-commit.png
Binary file
src/all-badges/cafe-commit/cafe-commit.ts โ†’ badges/cafe-commit/cafe-commit.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { Commit, Repo } from '../../collect/types.js'
+import { Commit, define, Repo } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['cafe-commit'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['cafe-commit'] as const,
+  present(data, grant) {
     const commits: { repo: Repo; commit: Commit }[] = []
 
     for (const repo of data.repos) {
@@ -28,5 +27,5 @@ export default new (class implements BadgePresenter {
         `I pushed a commit with "cafe" ${commits.length} times.`,
       ).evidence(text)
     }
-  }
-})()
+  },
+})
badges/chore-commit/chore-commit.png
Binary file
src/all-badges/chore-commit/chore-commit.ts โ†’ badges/chore-commit/chore-commit.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['chore-commit'] as const
-  tiers = false
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['chore-commit'] as const,
+  present(data, grant) {
     for (const repo of data.repos) {
       for (const commit of repo.commits) {
         if (/^chore\b/.test(commit.message)) {
@@ -16,5 +15,5 @@ export default new (class implements BadgePresenter {
         }
       }
     }
-  }
-})()
+  },
+})
badges/cosmetic-commit/cosmetic-commit.png
Binary file
src/all-badges/cosmetic-commit/cosmetic-commit.ts โ†’ badges/cosmetic-commit/cosmetic-commit.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { Commit } from '../../collect/types.js'
+import { Commit, define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['cosmetic-commit'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['cosmetic-commit'] as const,
+  present(data, grant) {
     const commits: Commit[] = []
 
     for (const repo of data.repos) {
@@ -23,8 +22,8 @@ export default new (class implements BadgePresenter {
         ...commits.sort(latest).slice(0, 6),
       )
     }
-  }
-})()
+  },
+})
 
 function latest(a: Commit, b: Commit) {
   return (
badges/covid-19/covid-19.png
Binary file
badges/covid-19/covid-19.ts
@@ -0,0 +1,15 @@
+import { define } from '#src'
+
+export default define({
+  url: import.meta.url,
+  badges: ['covid-19'] as const,
+  present(data, grant) {
+    const date = new Date(data.user.createdAt)
+    if (date.getFullYear() < 2020) {
+      grant(
+        'covid-19',
+        'I rolled before Covid-19: Survivor of the Great TP Shortage',
+      )
+    }
+  },
+})
badges/dead-commit/dead-commit.png
Binary file
src/all-badges/dead-commit/dead-commit.ts โ†’ badges/dead-commit/dead-commit.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { Commit, Repo } from '../../collect/types.js'
+import { Commit, define, Repo } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['dead-commit'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['dead-commit'] as const,
+  present(data, grant) {
     const commits: { repo: Repo; commit: Commit }[] = []
 
     for (const repo of data.repos) {
@@ -28,5 +27,5 @@ export default new (class implements BadgePresenter {
         `I pushed a commit with "dead" ${commits.length} times.`,
       ).evidence(text)
     }
-  }
-})()
+  },
+})
badges/delorean/delorean.png
Binary file
src/all-badges/delorean/delorean.ts โ†’ badges/delorean/delorean.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { Commit } from '../../collect/types.js'
+import { Commit, define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['delorean'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['delorean'] as const,
+  present(data, grant) {
     const commits: Commit[] = []
 
     for (const repo of data.repos) {
@@ -22,8 +21,8 @@ export default new (class implements BadgePresenter {
         'I committed on the day Doctor Emmett Brown invented the flux capacitor!',
       ).evidenceCommits(...commits.sort(latest).slice(0, 6))
     }
-  }
-})()
+  },
+})
 
 function latest(a: Commit, b: Commit) {
   return (
badges/emoji-only-commit/emoji-only-commit.png
Binary file
src/all-badges/emoji-only-commit/emoji-only-commit.ts โ†’ badges/emoji-only-commit/emoji-only-commit.ts
@@ -1,9 +1,9 @@
-import { Present, BadgePresenter } from '../../badges.js'
+import { define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['emoji-only-commit'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['emoji-only-commit'] as const,
+  present(data, grant) {
     const commits = data.repos.flatMap((repo) =>
       repo.commits.filter((commit) =>
         /^(\p{Emoji}|\s)+$/u.test(commit.message + commit.messageBody),
@@ -16,5 +16,5 @@ export default new (class implements BadgePresenter {
         'I used only emojis in my commit message.',
       ).evidenceCommitsWithMessage(...commits)
     }
-  }
-})()
+  },
+})
badges/favorite-word/favorite-word.png
Binary file
src/all-badges/favorite-word/favorite-word.ts โ†’ badges/favorite-word/favorite-word.ts
@@ -1,9 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['favorite-word'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['favorite-word'] as const,
+  present(data, grant) {
     const counts: Record<string, number> = {}
     for (const repo of data.repos) {
       for (const commit of repo.commits) {
@@ -28,5 +28,5 @@ export default new (class implements BadgePresenter {
           .map((p, i) => `${i + 1}. ${p[0]} (used ${p[1]} times)`)
           .join('\n'),
     )
-  }
-})()
+  },
+})
badges/fix-commit/fix-2.png
Binary file
badges/fix-commit/fix-3.png
Binary file
badges/fix-commit/fix-4.png
Binary file
badges/fix-commit/fix-5.png
Binary file
badges/fix-commit/fix-6+.png
Binary file
badges/fix-commit/fix-6.png
Binary file
src/all-badges/fix-commit/fix-commit.ts โ†’ badges/fix-commit/fix-commit.ts
@@ -1,19 +1,17 @@
-import { BadgePresenter, Grant, Present } from '../../badges.js'
+import { Commit, define } from '#src'
 
-import { Commit } from '../../collect/types.js'
-
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  tiers = true
-  badges = [
+export default define({
+  url: import.meta.url,
+  tiers: true,
+  badges: [
     'fix-2',
     'fix-3',
     'fix-4',
     'fix-5',
     'fix-6',
     'fix-6+', // For more than 6
-  ] as const
-  present: Present = (data, grant) => {
+  ] as const,
+  present(data, grant) {
     for (const repo of data.repos) {
       const sequences: Commit[][] = []
       let previousCommitDate = null
@@ -74,5 +72,5 @@ export default new (class implements BadgePresenter {
             .tier(6)
       }
     }
-  }
-})()
+  },
+})
badges/github-anniversary/github-anniversary-10.png
Binary file
badges/github-anniversary/github-anniversary-15.png
Binary file
badges/github-anniversary/github-anniversary-20.png
Binary file
badges/github-anniversary/github-anniversary-5.png
Binary file
src/all-badges/github-anniversary/github-anniversary.ts โ†’ badges/github-anniversary/github-anniversary.ts
@@ -1,15 +1,15 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  tiers = true
-  badges = [
+export default define({
+  url: import.meta.url,
+  tiers: true,
+  badges: [
     'github-anniversary-5',
     'github-anniversary-10',
     'github-anniversary-15',
     'github-anniversary-20',
-  ] as const
-  present: Present = (data, grant) => {
+  ] as const,
+  present(data, grant) {
     const createdAt = new Date(data.user.createdAt)
     const now = Date.now()
 
@@ -26,5 +26,5 @@ export default new (class implements BadgePresenter {
         grant(badge, `I joined GitHub ${years} years ago.`)
       }
     })
-  }
-})()
+  },
+})
badges/mass-delete-commit/mass-delete-commit-10k.png
Binary file
badges/mass-delete-commit/mass-delete-commit.png
Binary file
src/all-badges/mass-delete-commit/mass-delete-commit.ts โ†’ badges/mass-delete-commit/mass-delete-commit.ts
@@ -1,14 +1,10 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  tiers = true
-  badges = [
-    //
-    'mass-delete-commit',
-    'mass-delete-commit-10k',
-  ] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  tiers: true,
+  badges: ['mass-delete-commit', 'mass-delete-commit-10k'] as const,
+  present(data, grant) {
     for (const repo of data.repos) {
       for (const commit of repo.commits) {
         if (
@@ -30,5 +26,5 @@ export default new (class implements BadgePresenter {
         }
       }
     }
-  }
-})()
+  },
+})
badges/my-badges-contributor/my-badges-contributor.png
Binary file
src/all-badges/my-badges-contributor/my-badges-contributor.ts โ†’ badges/my-badges-contributor/my-badges-contributor.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { Pull } from '../../collect/types.js'
+import { define, Pull } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['my-badges-contributor'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['my-badges-contributor'] as const,
+  present(data, grant) {
     const pulls: Pull[] = []
     for (const pull of data.pulls) {
       if (
@@ -22,5 +21,5 @@ export default new (class implements BadgePresenter {
         'I contributed to <https://github.com/my-badges/my-badges>!',
       ).evidencePRs(...pulls)
     }
-  }
-})()
+  },
+})
badges/old-issue/old-issue-1.png
Binary file
badges/old-issue/old-issue-10.png
Binary file
badges/old-issue/old-issue-2.png
Binary file
badges/old-issue/old-issue-3.png
Binary file
badges/old-issue/old-issue-4.png
Binary file
badges/old-issue/old-issue-5.png
Binary file
badges/old-issue/old-issue-6.png
Binary file
badges/old-issue/old-issue-7.png
Binary file
badges/old-issue/old-issue-8.png
Binary file
badges/old-issue/old-issue-9.png
Binary file
src/all-badges/old-issue/old-issue.ts โ†’ badges/old-issue/old-issue.ts
@@ -1,11 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define, Issue } from '#src'
 
-import { Issue } from '../../collect/types.js'
-
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  tiers = true
-  badges = [
+export default define({
+  url: import.meta.url,
+  tiers: true,
+  badges: [
     'old-issue-1',
     'old-issue-2',
     'old-issue-3',
@@ -16,8 +14,8 @@ export default new (class implements BadgePresenter {
     'old-issue-8',
     'old-issue-9',
     'old-issue-10',
-  ] as const
-  present: Present = (data, grant) => {
+  ] as const,
+  present(data, grant) {
     const buckets: { [years: number]: Issue[] } = {}
 
     for (const issue of data.issues.sort(age)) {
@@ -41,8 +39,8 @@ export default new (class implements BadgePresenter {
         .evidenceIssuesWithTitles(...buckets[years])
         .tier(years)
     }
-  }
-})()
+  },
+})
 
 function age(a: Issue, b: Issue) {
   return new Date(a.closedAt).getTime() - new Date(b.closedAt).getTime()
badges/polite-coder/polite-coder.png
Binary file
src/all-badges/polite-coder/polite-coder.ts โ†’ badges/polite-coder/polite-coder.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { linkIssue } from '../../utils.js'
+import { define, linkIssue } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['polite-coder', 'rebel-coder'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['polite-coder', 'rebel-coder'] as const,
+  present(data, grant) {
     if (data.issues.length <= 10) {
       return
     }
@@ -31,5 +30,5 @@ export default new (class implements BadgePresenter {
           'Straight to the point!',
       )
     }
-  }
-})()
+  },
+})
badges/polite-coder/rebel-coder.png
Binary file
src/all-badges/pr-collaboration/pr-collaboration-10.png โ†’ badges/pr-collaboration/pr-collaboration-10.png
File renamed without changes
src/all-badges/pr-collaboration/pr-collaboration-15.png โ†’ badges/pr-collaboration/pr-collaboration-15.png
File renamed without changes
src/all-badges/pr-collaboration/pr-collaboration-20.png โ†’ badges/pr-collaboration/pr-collaboration-20.png
File renamed without changes
src/all-badges/pr-collaboration/pr-collaboration-25.png โ†’ badges/pr-collaboration/pr-collaboration-25.png
File renamed without changes
src/all-badges/pr-collaboration/pr-collaboration-5.png โ†’ badges/pr-collaboration/pr-collaboration-5.png
File renamed without changes
src/all-badges/pr-collaboration/pr-collaboration.ts โ†’ badges/pr-collaboration/pr-collaboration.ts
@@ -1,18 +1,16 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define, Pull } from '#src'
 
-import { Pull } from '../../collect/types.js'
-
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  tiers = true
-  badges = [
+export default define({
+  url: import.meta.url,
+  tiers: true,
+  badges: [
     'pr-collaboration-5',
     'pr-collaboration-10',
     'pr-collaboration-15',
     'pr-collaboration-20',
     'pr-collaboration-25',
-  ] as const
-  present: Present = (data, grant) => {
+  ] as const,
+  present(data, grant) {
     for (const pull of data.pulls.sort(byParticipantsCount)) {
       if (pull.participants.totalCount >= 5) {
         grant(
@@ -55,8 +53,8 @@ export default new (class implements BadgePresenter {
           .tier(5)
       }
     }
-  }
-})()
+  },
+})
 
 function byParticipantsCount(a: Pull, b: Pull) {
   return a.participants.totalCount - b.participants.totalCount
badges/public-keys/public-keys-1.png
Binary file
badges/public-keys/public-keys-2.png
Binary file
badges/public-keys/public-keys-3.png
Binary file
badges/public-keys/public-keys-4.png
Binary file
badges/public-keys/public-keys-5.png
Binary file
src/all-badges/public-keys/public-keys.ts โ†’ badges/public-keys/public-keys.ts
@@ -1,16 +1,16 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  tiers = true
-  badges = [
+export default define({
+  url: import.meta.url,
+  tiers: true,
+  badges: [
     'public-keys-1',
     'public-keys-2',
     'public-keys-3',
     'public-keys-4',
     'public-keys-5',
-  ] as const
-  present: Present = (data, grant) => {
+  ] as const,
+  present(data, grant) {
     const count = data.user.publicKeys?.totalCount ?? 0
     if (count == 1) {
       grant('public-keys-1', 'I have one public key').tier(1)
@@ -27,5 +27,5 @@ export default new (class implements BadgePresenter {
     if (count >= 5) {
       grant('public-keys-5', 'I have five or more public keys').tier(5)
     }
-  }
-})()
+  },
+})
badges/revert-revert-commit/revert-revert-commit.png
Binary file
src/all-badges/revert-revert-commit/revert-revert-commit.ts โ†’ badges/revert-revert-commit/revert-revert-commit.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { Commit } from '../../collect/types.js'
+import { Commit, define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['revert-revert-commit'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['revert-revert-commit'] as const,
+  present(data, grant) {
     const commits: Commit[] = []
 
     for (const repo of data.repos) {
@@ -21,5 +20,5 @@ export default new (class implements BadgePresenter {
         'I reverted a revert commit.',
       ).evidenceCommits(...commits)
     }
-  }
-})()
+  },
+})
badges/spooky-commit/spooky-commit.png
Binary file
src/all-badges/spooky-commit/spooky-commit.ts โ†’ badges/spooky-commit/spooky-commit.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { Commit } from '../../collect/types.js'
+import { Commit, define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['spooky-commit'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['spooky-commit'] as const,
+  present(data, grant) {
     const commits: Commit[] = []
 
     for (const repo of data.repos) {
@@ -22,8 +21,8 @@ export default new (class implements BadgePresenter {
         'I committed on the Halloween! Boo!',
       ).evidenceCommits(...commits.sort(latest).slice(0, 6))
     }
-  }
-})()
+  },
+})
 
 function latest(a: Commit, b: Commit) {
   return (
badges/star-gazer/star-gazer.png
Binary file
badges/star-gazer/star-gazer.ts
@@ -0,0 +1,13 @@
+import { define } from '#src'
+
+export default define({
+  url: import.meta.url,
+  badges: ['star-gazer'] as const,
+  present(data, grant) {
+    if (data.user.starredRepositories?.totalCount >= 1000) {
+      grant('star-gazer', "I'm a star gazer!").evidence(
+        "I've starred over 1000 repositories!",
+      )
+    }
+  },
+})
badges/stars/stars-100.png
Binary file
badges/stars/stars-1000.png
Binary file
badges/stars/stars-10000.png
Binary file
badges/stars/stars-2000.png
Binary file
badges/stars/stars-20000.png
Binary file
badges/stars/stars-500.png
Binary file
badges/stars/stars-5000.png
Binary file
src/all-badges/stars/stars.ts โ†’ badges/stars/stars.ts
@@ -1,11 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define, Repo } from '#src'
 
-import { Repo } from '../../collect/types.js'
-
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  tiers = true
-  badges = [
+export default define({
+  url: import.meta.url,
+  tiers: true,
+  badges: [
     'stars-100',
     'stars-500',
     'stars-1000',
@@ -13,8 +11,8 @@ export default new (class implements BadgePresenter {
     'stars-5000',
     'stars-10000',
     'stars-20000',
-  ] as const
-  present: Present = (data, grant) => {
+  ] as const,
+  present(data, grant) {
     const repos = data.repos.sort(asc).filter(withStars)
     let totalStars = 0
 
@@ -57,8 +55,8 @@ export default new (class implements BadgePresenter {
         .evidence(text(repos, 20_000))
         .tier(7)
     }
-  }
-})()
+  },
+})
 
 function asc(a: Repo, b: Repo) {
   return (a.stargazers_count || 0) - (b.stargazers_count || 0)
badges/the-ultimate-question/the-ultimate-question.png
Binary file
src/all-badges/the-ultimate-question/the-ultimate-question.ts โ†’ badges/the-ultimate-question/the-ultimate-question.ts
@@ -1,10 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { Issue, Pull } from '../../collect/types.js'
+import { define, Issue, Pull } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['the-ultimate-question'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['the-ultimate-question'] as const,
+  present(data, grant) {
     const list: (Issue | Pull)[] = []
 
     for (const issue of data.issues) {
@@ -20,8 +19,8 @@ export default new (class implements BadgePresenter {
         'I found the answer to the ultimate question of life, the universe, and everything!',
       ).evidence(list.map((x) => `- ${link(x)}`).join('\n'))
     }
-  }
-})()
+  },
+})
 
 function link(x: Issue | Pull): string {
   return `<a href="https://github.com/${x.repository.owner.login}/${x.repository.name}/issues/${x.number}">#${x.number}</a>`
badges/this-is-fine/this-is-fine.png
Binary file
src/all-badges/this-is-fine/this-is-fine.ts โ†’ badges/this-is-fine/this-is-fine.ts
@@ -1,11 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define, Pull } from '#src'
 
-import { Pull } from '../../collect/types.js'
-
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['this-is-fine'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['this-is-fine'] as const,
+  present(data, grant) {
     const pulls: Pull[] = []
 
     for (const pull of data.pulls) {
@@ -37,5 +35,5 @@ export default new (class implements BadgePresenter {
         'I merged a PR with failing checks',
       ).evidencePRsWithTitle(...pulls)
     }
-  }
-})()
+  },
+})
badges/time-of-commit/evening-commits.png
Binary file
badges/time-of-commit/midnight-commits.png
Binary file
badges/time-of-commit/morning-commits.png
Binary file
badges/time-of-commit/sleepy-coder.png
Binary file
src/all-badges/time-of-commit/time-of-commit.ts โ†’ badges/time-of-commit/time-of-commit.ts
@@ -1,15 +1,14 @@
-import { BadgePresenter, Present } from '../../badges.js'
-import { Commit, User } from '../../collect/types.js'
+import { Commit, define, User } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = [
+export default define({
+  url: import.meta.url,
+  badges: [
     'midnight-commits',
     'morning-commits',
     'evening-commits',
     'sleepy-coder',
-  ] as const
-  present: Present = (data, grant) => {
+  ] as const,
+  present(data, grant) {
     const morningCommits: Commit[] = []
     const eveningCommits: Commit[] = []
     const midnightCommits: Commit[] = []
@@ -58,8 +57,8 @@ export default new (class implements BadgePresenter {
         ...midnightCommits.sort(latest).slice(0, 6),
       )
     }
-  }
-})()
+  },
+})
 
 function latest(a: Commit, b: Commit) {
   return (
badges/yeti/yeti.png
Binary file
src/all-badges/yeti/yeti.ts โ†’ badges/yeti/yeti.ts
@@ -1,9 +1,9 @@
-import { BadgePresenter, Present } from '../../badges.js'
+import { define } from '#src'
 
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['yeti'] as const
-  present: Present = (data, grant) => {
+export default define({
+  url: import.meta.url,
+  badges: ['yeti'] as const,
+  present(data, grant) {
     for (const repo of data.repos) {
       for (const commit of repo.commits) {
         if (/yeti/i.test(commit.message)) {
@@ -12,5 +12,5 @@ export default new (class implements BadgePresenter {
         }
       }
     }
-  }
-})()
+  },
+})
src/all-badges/index.ts โ†’ badges/index.ts
@@ -1,28 +1,28 @@
-export const allBadges = [
+export default [
   await import('./abc-commit/abc-commit.js'),
-  await import('./stars/stars.js'),
-  await import('./time-of-commit/time-of-commit.js'),
-  await import('./github-anniversary/github-anniversary.js'),
-  await import('./yeti/yeti.js'),
-  await import('./star-gazer/star-gazer.js'),
-  await import('./dead-commit/dead-commit.js'),
   await import('./bad-words/bad-words.js'),
-  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'),
+  await import('./cafe-commit/cafe-commit.js'),
   await import('./chore-commit/chore-commit.js'),
-  await import('./delorean/delorean.js'),
+  await import('./cosmetic-commit/cosmetic-commit.js'),
   await import('./covid-19/covid-19.js'),
-  await import('./pr-collaboration/pr-collaboration.js'),
-  await import('./public-keys/public-keys.js'),
-  await import('./old-issue/old-issue.js'),
-  await import('./this-is-fine/this-is-fine.js'),
-  await import('./the-ultimate-question/the-ultimate-question.js'),
+  await import('./dead-commit/dead-commit.js'),
+  await import('./delorean/delorean.js'),
+  await import('./emoji-only-commit/emoji-only-commit.js'),
   await import('./favorite-word/favorite-word.js'),
+  await import('./fix-commit/fix-commit.js'),
+  await import('./github-anniversary/github-anniversary.js'),
+  await import('./mass-delete-commit/mass-delete-commit.js'),
+  await import('./my-badges-contributor/my-badges-contributor.js'),
+  await import('./old-issue/old-issue.js'),
   await import('./polite-coder/polite-coder.js'),
-  await import('./emoji-only-commit/emoji-only-commit.js'),
+  await import('./pr-collaboration/pr-collaboration.js'),
+  await import('./public-keys/public-keys.js'),
+  await import('./revert-revert-commit/revert-revert-commit.js'),
   await import('./spooky-commit/spooky-commit.js'),
-  await import('./cosmetic-commit/cosmetic-commit.js'),
-  await import('./cafe-commit/cafe-commit.js')
+  await import('./star-gazer/star-gazer.js'),
+  await import('./stars/stars.js'),
+  await import('./the-ultimate-question/the-ultimate-question.js'),
+  await import('./this-is-fine/this-is-fine.js'),
+  await import('./time-of-commit/time-of-commit.js'),
+  await import('./yeti/yeti.js'),
 ] as const
scripts/check-images.mjs
@@ -0,0 +1,38 @@
+#!/usr/bin/env node
+
+import allBadges from '#badges'
+import fs from 'node:fs'
+import { fileURLToPath } from 'node:url'
+import * as path from 'node:path'
+import { imageDimensionsFromStream } from 'image-dimensions'
+
+const expectedDimensions = 256
+
+const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
+
+let ok = true
+for (const { default: b } of allBadges) {
+  const dirname = path.basename(path.dirname(fileURLToPath(b.url)))
+  for (const id of b.badges) {
+    const imagePath = path.join('badges', dirname, `${id}.png`)
+    const rootPath = path.join(root, imagePath)
+    if (!fs.existsSync(rootPath)) {
+      console.error(`Missing image for badge "${id}" at ${rootPath}`)
+      ok = false
+    }
+    const { width, height } = await imageDimensionsFromStream(
+      fs.createReadStream(rootPath),
+    )
+    if (width !== expectedDimensions || height !== expectedDimensions) {
+      console.error(
+        `Bad image dimensions for badge ${id}.png: ${width}x${height} (expected: ${expectedDimensions}x${expectedDimensions})`,
+      )
+      ok = false
+    }
+  }
+}
+if (!ok) {
+  process.exit(1)
+} else {
+  console.log('All images exist and have the expected dimensions')
+}
src/all-badges/cafe-commit/cafe-commit.png
Binary file
src/all-badges/covid-19/covid-19.ts
@@ -1,15 +0,0 @@
-import { BadgePresenter, Present } from '../../badges.js'
-
-export default new (class implements BadgePresenter {
-  url = new URL(import.meta.url)
-  badges = ['covid-19'] as const
-  present: Present = (data, grant) => {
-    const date = new Date(data.user.createdAt)
-    if (date.getFullYear() < 2020) {
-      grant(
-        'covid-19',
-        'I rolled before Covid-19: Survivor of the Great TP Shortage',
-      )
-    }
-  }
-})()
src/all-badges/star-gazer/star-gazer.ts
@@ -1,13 +0,0 @@
-import { BadgePresenter, Present } from '../../badges.js'
-
-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) {
-      grant('star-gazer', "I'm a star gazer!").evidence(
-        "I've starred over 1000 repositories!",
-      )
-    }
-  }
-})()
src/all-badges/README
@@ -0,0 +1,1 @@
+Temporary folder for migration to new badges location.
src/badges.ts
@@ -1,25 +1,26 @@
-import { allBadges } from './all-badges/index.js'
-import { expectType, linkCommit, linkIssue, linkPull } from './utils.js'
+import allBadges from '#badges'
+import { linkCommit, linkIssue, linkPull } from './utils.js'
 import { Commit, Data, Issue, Pull } from './collect/types.js'
 
-for (const {
-  default: { badges },
-} of allBadges) {
-  expectType<readonly [string, ...string[]]>(badges)
-}
+export type Presenters = (typeof allBadges)[number]['default']
+
+export type ID = Presenters['badges'][number]
 
-export type ID = (typeof allBadges)[number]['default']['badges'][number]
+export type List = readonly [string, ...string[]]
 
-export interface BadgePresenter {
-  url: URL
+export type Presenter<B extends List> = {
+  url: string
+  badges: B
   tiers?: boolean
-  badges: unknown
-  present: Present
+  present: (
+    data: Data,
+    grant: (id: B[number], desc: string) => Evidence,
+  ) => void
 }
 
-export type Grant = ReturnType<typeof badgeCollection>
-
-export type Present = (data: Data, grant: Grant) => void
+export function define<B extends List>(presenter: Presenter<B>): Presenter<B> {
+  return presenter
+}
 
 export type Badge = {
   id: ID
@@ -29,23 +30,7 @@ export type Badge = {
   image: string
 }
 
-export function badgeCollection(newBadges: Badge[]) {
-  return function grant(id: ID, desc: string) {
-    const badge: Badge = {
-      id,
-      tier: 0,
-      desc,
-      body: '',
-      image: '',
-    }
-    if (!newBadges.some((x) => x.id === id)) {
-      newBadges.push(badge)
-    }
-    return new Evidence(badge)
-  }
-}
-
-class Evidence {
+export class Evidence {
   constructor(private badge: Badge) {}
 
   tier(tier: number) {
src/check-images.ts
@@ -1,27 +0,0 @@
-import { allBadges } from './all-badges/index.js'
-import fs from 'node:fs'
-import { fileURLToPath } from 'url'
-import * as path from 'path'
-
-void (async function main() {
-  const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..')
-
-  let foundMissing = false
-  for (const { default: b } of allBadges) {
-    const dirname = path.basename(path.dirname(fileURLToPath(b.url)))
-    for (const id of b.badges) {
-      const imagePath = path.join('src/all-badges', dirname, `${id}.png`)
-      const rootPath = path.join(root, imagePath)
-      if (!fs.existsSync(rootPath)) {
-        console.error(`Missing image for badge "${id}" at ${rootPath}`)
-        foundMissing = true
-      } else {
-        console.log(`<img src="${imagePath}" alt="${id}" width="42">`)
-      }
-    }
-  }
-  if (foundMissing) {
-    process.exit(1)
-    return
-  }
-})()
src/index.ts
@@ -0,0 +1,3 @@
+export { define } from './badges.js'
+export { Repo, User, Issue, Pull, Commit } from './collect/types.js'
+export { linkCommit, linkIssue, linkPull } from './utils.js'
src/main.ts
@@ -1,12 +1,12 @@
 #!/usr/bin/env node
 
+import allBadges from '#badges'
 import minimist from 'minimist'
 import { Octokit } from 'octokit'
 import { retry } from '@octokit/plugin-retry'
 import { throttling } from '@octokit/plugin-throttling'
 import { presentBadges } from './present-badges.js'
 import { getData } from './get-data.js'
-import { allBadges } from './all-badges/index.js'
 import { getUserBadges, gitClone, gitPush, thereAreChanges } from './repo.js'
 import { updateBadges } from './update-badges.js'
 import { updateReadme } from './update-readme.js'
src/present-badges.ts
@@ -1,12 +1,11 @@
-import { Badge, badgeCollection, BadgePresenter, ID } from './badges.js'
-import { allBadges } from './all-badges/index.js'
+import { Badge, Evidence, ID, List, Presenter } from './badges.js'
 import path from 'node:path'
 import { fileURLToPath } from 'node:url'
 import { parseMask } from './utils.js'
 import { Data } from './collect/types.js'
 
-export const presentBadges = (
-  presenters: BadgePresenter[],
+export const presentBadges = <P extends Presenter<List>>(
+  presenters: P[],
   data: Data,
   userBadges: Badge[],
   pickBadges: string[],
@@ -16,6 +15,8 @@ export const presentBadges = (
   for (const presenter of presenters) {
     const newBadges: Badge[] = []
     const grant = badgeCollection(newBadges)
+
+    // @ts-ignore As `id: string` is not assignable to `id: ID`.
     presenter.present(data, grant)
 
     if (newBadges.length === 0) {
@@ -25,11 +26,10 @@ export const presentBadges = (
     // Enhance badges with image URLs.
     for (const b of newBadges) {
       const baseDir = path.basename(path.dirname(fileURLToPath(presenter.url)))
-      b.image = `https://github.com/my-badges/my-badges/blob/master/src/all-badges/${baseDir}/${b.id}.png?raw=true`
+      b.image = `https://github.com/my-badges/my-badges/blob/master/badges/${baseDir}/${b.id}.png?raw=true`
     }
 
-    const badgeFromPresenter = (x: Badge) =>
-      (presenter.badges as ID[]).includes(x.id)
+    const badgeFromPresenter = (x: Badge) => presenter.badges.includes(x.id)
 
     // Merge existing userBadges with newBadges.
     if (compact && presenter.tiers) {
@@ -76,3 +76,19 @@ export const presentBadges = (
 
   return userBadges
 }
+
+export function badgeCollection(newBadges: Badge[]) {
+  return function grant(id: ID, desc: string) {
+    const badge: Badge = {
+      id,
+      tier: 0,
+      desc,
+      body: '',
+      image: '',
+    }
+    if (!newBadges.some((x) => x.id === id)) {
+      newBadges.push(badge)
+    }
+    return new Evidence(badge)
+  }
+}
src/utils.ts
@@ -26,8 +26,6 @@ export function quoteAttr(s: string) {
     .replace(/[\r\n]/g, '&#13;')
 }
 
-export const expectType = <T>(expression: T) => void 0
-
 export function parseMask(value: string): RegExp {
   return new RegExp(`^${value}$`.replace('*', '.+'))
 }
test/present-badges.test.ts
@@ -1,7 +1,7 @@
 import * as assert from 'node:assert'
 import { describe, it } from 'node:test'
 import { presentBadges } from '../src/present-badges.js'
-import { Badge, BadgePresenter } from '../src/badges.js'
+import { Badge, define, List, Presenter } from '../src/badges.js'
 import { Data } from '../src/collect/types.js'
 
 describe('present-badges', () => {
@@ -35,7 +35,7 @@ describe('present-badges', () => {
 
   it('presentBadges() applies `pick`', async () => {
     const userBadges = presentBadges(
-      [await import('../src/all-badges/stars/stars.js')].map((m) => m.default),
+      [await import('#badges/stars/stars.js')].map((m) => m.default),
       data,
       [],
       ['stars-100', 'stars-500'],
@@ -55,7 +55,7 @@ describe('present-badges', () => {
           '\n' +
           "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
         image:
-          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/stars/stars-100.png?raw=true',
+          'https://github.com/my-badges/my-badges/blob/master/badges/stars/stars-100.png?raw=true',
       },
       {
         id: 'stars-500',
@@ -68,14 +68,14 @@ describe('present-badges', () => {
           '\n' +
           "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
         image:
-          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/stars/stars-500.png?raw=true',
+          'https://github.com/my-badges/my-badges/blob/master/badges/stars/stars-500.png?raw=true',
       },
     ])
   })
 
   it('presentBadges() applies `omit`', async () => {
     const userBadges = presentBadges(
-      [await import('../src/all-badges/stars/stars.js')].map((m) => m.default),
+      [await import('#badges/stars/stars.js')].map((m) => m.default),
       data,
       [],
       ['stars-100', 'stars-500'],
@@ -95,14 +95,14 @@ describe('present-badges', () => {
           '\n' +
           "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
         image:
-          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/stars/stars-100.png?raw=true',
+          'https://github.com/my-badges/my-badges/blob/master/badges/stars/stars-100.png?raw=true',
       },
     ])
   })
 
   it('presentBadges() supports masks for `omit` && `pick`', async () => {
     const userBadges = presentBadges(
-      [await import('../src/all-badges/stars/stars.js')].map((m) => m.default),
+      [await import('#badges/stars/stars.js')].map((m) => m.default),
       data,
       [],
       ['stars-*'],
@@ -122,7 +122,7 @@ describe('present-badges', () => {
           '\n' +
           "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
         image:
-          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/stars/stars-100.png?raw=true',
+          'https://github.com/my-badges/my-badges/blob/master/badges/stars/stars-100.png?raw=true',
       },
       {
         id: 'stars-500',
@@ -135,14 +135,14 @@ describe('present-badges', () => {
           '\n' +
           "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
         image:
-          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/stars/stars-500.png?raw=true',
+          'https://github.com/my-badges/my-badges/blob/master/badges/stars/stars-500.png?raw=true',
       },
     ])
   })
 
   it('presentBadges() applies `compact`', async () => {
     const userBadges = presentBadges(
-      [await import('../src/all-badges/stars/stars.js')].map((m) => m.default),
+      [await import('#badges/stars/stars.js')].map((m) => m.default),
       data,
       [],
       ['stars-1000', 'stars-2000', 'stars-5000'],
@@ -163,28 +163,28 @@ describe('present-badges', () => {
           '\n' +
           "<sup>I have push, maintainer or admin permissions, so I'm definitely an author.<sup>\n",
         image:
-          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/stars/stars-2000.png?raw=true',
+          'https://github.com/my-badges/my-badges/blob/master/badges/stars/stars-2000.png?raw=true',
       },
     ])
   })
 
   it('presentBadges() keeps existing order of badges', async () => {
-    const dumpPresenter1: BadgePresenter = {
-      url: new URL('file:///tmp/dump.js'),
-      badges: ['a-commit', 'ab-commit', 'abc-commit'],
+    const dumpPresenter1 = define({
+      url: 'file:///tmp/dump.js',
+      badges: ['a-commit', 'ab-commit', 'abc-commit'] as const,
       present: (_, grant) => {
         grant('a-commit', 'a')
         grant('ab-commit', 'ab')
         grant('abc-commit', 'abc')
       },
-    }
-    const dumpPresenter2: BadgePresenter = {
-      url: new URL('file:///tmp/dump.js'),
-      badges: ['this-is-fine'],
+    })
+    const dumpPresenter2 = define({
+      url: 'file:///tmp/dump.js',
+      badges: ['this-is-fine'] as const,
       present: (_, grant) => {
         grant('this-is-fine', 'this is fine')
       },
-    }
+    })
 
     const oldUserBadges: Badge[] = [
       {
test/stars.test.ts
@@ -1,9 +1,9 @@
 import * as assert from 'node:assert'
 import { describe, it } from 'node:test'
-import starsPresenter from '../src/all-badges/stars/stars.js'
-import { Badge, badgeCollection } from '../src/badges.js'
-
+import starsPresenter from '#badges/stars/stars.js'
+import { badgeCollection } from '../src/present-badges.js'
 import { Data } from '../src/collect/types.js'
+import { Badge } from '../src/badges.js'
 
 describe('stars', () => {
   it('counts and renders as expected', () => {
test/update-readme.test.ts
@@ -2,8 +2,8 @@ import * as assert from 'node:assert'
 import { describe, it } from 'node:test'
 import { generateReadme } from '../src/update-readme.js'
 import type { Badge } from '../src/badges.js'
-import abcPresenter from '../src/all-badges/abc-commit/abc-commit.js'
-import { badgeCollection } from '../src/badges.js'
+import abcPresenter from '#badges/abc-commit/abc-commit.js'
+import { badgeCollection } from '../src/present-badges.js'
 
 describe('generateReadme()', () => {
   it('injects badges to md contents', () => {
CONTRIBUTING.md
@@ -2,11 +2,11 @@
 
 If you want to contribute a badge:
 
-- Add your badge to the [all-badges](./src/all-badges) folder.
-- Add your badge to the [index.ts](./src/all-badges/index.ts) file.
+- Add your badge to the [badges](./badges) folder.
+- Add your badge to the [index.ts](./badges/index.ts) file.
 - Any badge images are welcome (png, 256x256px).
 
-Here is an [example of a pull request](https://github.com/my-badges/my-badges/pull/1) adding a new badge.
+Example of a simple badge: [yeti.ts](./badges/yeti/yeti.ts).
 
 ## How to test locally?
 
@@ -19,14 +19,14 @@ npm run build
 Run main.js with next command:
 
 ```sh
-node dist/main.js your-username
+node dist/src/main.js your-username
 ```
 
 This command will collect your data and save it to `data/your-username.json` file.
 You can skip recollecting the data with `--data` flag.
 
 ```sh
-node dist/main.js --data data/your-username.json
+node dist/src/main.js --data data/your-username.json
 ```
 
 ## How to create a badge image?
package-lock.json
@@ -15,15 +15,15 @@
         "octokit": "^3.1.1"
       },
       "bin": {
-        "update-my-badges": "dist/main.js"
+        "update-my-badges": "dist/src/main.js"
       },
       "devDependencies": {
         "@octokit/graphql-schema": "^14.33.0",
         "@types/minimist": "^1.2.3",
         "@types/node": "^20.8.0",
         "c8": "^8.0.1",
-        "copyfiles": "^2.4.1",
         "fast-glob": "^3.3.1",
+        "image-dimensions": "^2.3.0",
         "prettier": "^3.0.3",
         "ts-node": "^10.9.1",
         "typescript": "^5.2.2"
@@ -800,17 +800,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/cliui": {
-      "version": "7.0.4",
-      "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
-      "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
-      "dev": true,
-      "dependencies": {
-        "string-width": "^4.2.0",
-        "strip-ansi": "^6.0.0",
-        "wrap-ansi": "^7.0.0"
-      }
-    },
     "node_modules/color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -841,31 +830,6 @@
       "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
       "dev": true
     },
-    "node_modules/copyfiles": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz",
-      "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==",
-      "dev": true,
-      "dependencies": {
-        "glob": "^7.0.5",
-        "minimatch": "^3.0.3",
-        "mkdirp": "^1.0.4",
-        "noms": "0.0.0",
-        "through2": "^2.0.1",
-        "untildify": "^4.0.0",
-        "yargs": "^16.1.0"
-      },
-      "bin": {
-        "copyfiles": "copyfiles",
-        "copyup": "copyfiles"
-      }
-    },
-    "node_modules/core-util-is": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
-      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
-      "dev": true
-    },
     "node_modules/create-require": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
@@ -1075,6 +1039,22 @@
       "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
       "dev": true
     },
+    "node_modules/image-dimensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/image-dimensions/-/image-dimensions-2.3.0.tgz",
+      "integrity": "sha512-8Ar3lsO6+/JLfnUeHnR8Jp/IyQR85Jut5t4Swy1yiXNwj/xM9h5V53v5KE/m/ZSMG4qGRopnSy37uPzKyQCv0A==",
+      "dev": true,
+      "license": "MIT",
+      "bin": {
+        "image-dimensions": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/indent-string": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@@ -1146,12 +1126,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/isarray": {
-      "version": "0.0.1",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
-      "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
-      "dev": true
-    },
     "node_modules/isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
@@ -1355,33 +1329,11 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/mkdirp": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
-      "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
-      "dev": true,
-      "bin": {
-        "mkdirp": "bin/cmd.js"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/ms": {
       "version": "2.1.3",
       "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
       "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
     },
-    "node_modules/noms": {
-      "version": "0.0.0",
-      "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz",
-      "integrity": "sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==",
-      "dev": true,
-      "dependencies": {
-        "inherits": "^2.0.1",
-        "readable-stream": "~1.0.31"
-      }
-    },
     "node_modules/octokit": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/octokit/-/octokit-3.1.1.tgz",
@@ -1507,12 +1459,6 @@
         "url": "https://github.com/prettier/prettier?sponsor=1"
       }
     },
-    "node_modules/process-nextick-args": {
-      "version": "2.0.1",
-      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
-      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
-      "dev": true
-    },
     "node_modules/queue-microtask": {
       "version": "1.2.3",
       "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -1533,18 +1479,6 @@
         }
       ]
     },
-    "node_modules/readable-stream": {
-      "version": "1.0.34",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
-      "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==",
-      "dev": true,
-      "dependencies": {
-        "core-util-is": "~1.0.0",
-        "inherits": "~2.0.1",
-        "isarray": "0.0.1",
-        "string_decoder": "~0.10.x"
-      }
-    },
     "node_modules/require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -1673,12 +1607,6 @@
       "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
       "dev": true
     },
-    "node_modules/string_decoder": {
-      "version": "0.10.31",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
-      "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==",
-      "dev": true
-    },
     "node_modules/string-width": {
       "version": "4.2.3",
       "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@@ -1731,52 +1659,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/through2": {
-      "version": "2.0.5",
-      "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
-      "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==",
-      "dev": true,
-      "dependencies": {
-        "readable-stream": "~2.3.6",
-        "xtend": "~4.0.1"
-      }
-    },
-    "node_modules/through2/node_modules/isarray": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
-      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
-      "dev": true
-    },
-    "node_modules/through2/node_modules/readable-stream": {
-      "version": "2.3.8",
-      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
-      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
-      "dev": true,
-      "dependencies": {
-        "core-util-is": "~1.0.0",
-        "inherits": "~2.0.3",
-        "isarray": "~1.0.0",
-        "process-nextick-args": "~2.0.0",
-        "safe-buffer": "~5.1.1",
-        "string_decoder": "~1.1.1",
-        "util-deprecate": "~1.0.1"
-      }
-    },
-    "node_modules/through2/node_modules/safe-buffer": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
-      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
-      "dev": true
-    },
-    "node_modules/through2/node_modules/string_decoder": {
-      "version": "1.1.1",
-      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
-      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
-      "dev": true,
-      "dependencies": {
-        "safe-buffer": "~5.1.0"
-      }
-    },
     "node_modules/to-regex-range": {
       "version": "5.0.1",
       "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -1865,21 +1747,6 @@
       "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz",
       "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
     },
-    "node_modules/untildify": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
-      "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
-      "dev": true,
-      "engines": {
-        "node": ">=8"
-      }
-    },
-    "node_modules/util-deprecate": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
-      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
-      "dev": true
-    },
     "node_modules/v8-compile-cache-lib": {
       "version": "3.0.1",
       "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
@@ -1947,15 +1814,6 @@
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
       "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
     },
-    "node_modules/xtend": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
-      "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
-      "dev": true,
-      "engines": {
-        "node": ">=0.4"
-      }
-    },
     "node_modules/y18n": {
       "version": "5.0.8",
       "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
@@ -1970,33 +1828,6 @@
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
       "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
     },
-    "node_modules/yargs": {
-      "version": "16.2.0",
-      "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
-      "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
-      "dev": true,
-      "dependencies": {
-        "cliui": "^7.0.2",
-        "escalade": "^3.1.1",
-        "get-caller-file": "^2.0.5",
-        "require-directory": "^2.1.1",
-        "string-width": "^4.2.0",
-        "y18n": "^5.0.5",
-        "yargs-parser": "^20.2.2"
-      },
-      "engines": {
-        "node": ">=10"
-      }
-    },
-    "node_modules/yargs-parser": {
-      "version": "20.2.9",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
-      "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
-      "dev": true,
-      "engines": {
-        "node": ">=10"
-      }
-    },
     "node_modules/yn": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
package.json
@@ -4,7 +4,12 @@
   "description": "Generate badges for your GitHub projects",
   "type": "module",
   "bin": {
-    "update-my-badges": "dist/main.js"
+    "update-my-badges": "dist/src/main.js"
+  },
+  "imports": {
+    "#src": "./dist/src/index.js",
+    "#badges": "./dist/badges/index.js",
+    "#badges/*": "./dist/badges/*"
   },
   "scripts": {
     "fmt": "prettier --write .",
@@ -12,7 +17,7 @@
     "start": "tsc --watch",
     "tsc": "tsc",
     "build": "tsc",
-    "check-images": "node dist/check-images.js",
+    "check-images": "node scripts/check-images.mjs",
     "test": "npm run test:unit",
     "test:unit": "c8 -r lcov -r text -o coverage -x scripts -x test node --loader ts-node/esm --experimental-specifier-resolution=node scripts/test.mjs"
   },
@@ -28,6 +33,7 @@
     "@types/node": "^20.8.0",
     "c8": "^8.0.1",
     "fast-glob": "^3.3.1",
+    "image-dimensions": "^2.3.0",
     "prettier": "^3.0.3",
     "ts-node": "^10.9.1",
     "typescript": "^5.2.2"
README.md
@@ -14,71 +14,23 @@ But how does those badges look like? Take a look [here](https://github.com/anton
 or [here](https://github.com/antongolub).
 
 <p>
-<img src="src/all-badges/abc-commit/a-commit.png" alt="a-commit" width="42">
-<img src="src/all-badges/abc-commit/ab-commit.png" alt="ab-commit" width="42">
-<img src="src/all-badges/abc-commit/abc-commit.png" alt="abc-commit" width="42">
-<img src="src/all-badges/abc-commit/abcd-commit.png" alt="abcd-commit" width="42">
-<img src="src/all-badges/abc-commit/abcde-commit.png" alt="abcde-commit" width="42">
-<img src="src/all-badges/abc-commit/abcdef-commit.png" alt="abcdef-commit" width="42">
-<img src="src/all-badges/stars/stars-100.png" alt="stars-100" width="42">
-<img src="src/all-badges/stars/stars-500.png" alt="stars-500" width="42">
-<img src="src/all-badges/stars/stars-1000.png" alt="stars-1000" width="42">
-<img src="src/all-badges/stars/stars-2000.png" alt="stars-2000" width="42">
-<img src="src/all-badges/stars/stars-5000.png" alt="stars-5000" width="42">
-<img src="src/all-badges/stars/stars-10000.png" alt="stars-10000" width="42">
-<img src="src/all-badges/stars/stars-20000.png" alt="stars-20000" width="42">
-<img src="src/all-badges/time-of-commit/midnight-commits.png" alt="midnight-commits" width="42">
-<img src="src/all-badges/time-of-commit/morning-commits.png" alt="morning-commits" width="42">
-<img src="src/all-badges/time-of-commit/evening-commits.png" alt="evening-commits" width="42">
-<img src="src/all-badges/time-of-commit/sleepy-coder.png" alt="sleepy-coder" width="42">
-<img src="src/all-badges/github-anniversary/github-anniversary-5.png" alt="github-anniversary-5" width="42">
-<img src="src/all-badges/github-anniversary/github-anniversary-10.png" alt="github-anniversary-10" width="42">
-<img src="src/all-badges/github-anniversary/github-anniversary-15.png" alt="github-anniversary-15" width="42">
-<img src="src/all-badges/github-anniversary/github-anniversary-20.png" alt="github-anniversary-20" width="42">
-<img src="src/all-badges/yeti/yeti.png" alt="yeti" width="42">
-<img src="src/all-badges/star-gazer/star-gazer.png" alt="star-gazer" width="42">
-<img src="src/all-badges/dead-commit/dead-commit.png" alt="dead-commit" width="42">
-<img src="src/all-badges/bad-words/bad-words.png" alt="bad-words" width="42">
-<img src="src/all-badges/mass-delete-commit/mass-delete-commit.png" alt="mass-delete-commit" width="42">
-<img src="src/all-badges/mass-delete-commit/mass-delete-commit-10k.png" alt="mass-delete-commit-10k" width="42">
-<img src="src/all-badges/revert-revert-commit/revert-revert-commit.png" alt="revert-revert-commit" width="42">
-<img src="src/all-badges/my-badges-contributor/my-badges-contributor.png" alt="my-badges-contributor" width="42">
-<img src="src/all-badges/fix-commit/fix-2.png" alt="fix-2" width="42">
-<img src="src/all-badges/fix-commit/fix-3.png" alt="fix-3" width="42">
-<img src="src/all-badges/fix-commit/fix-4.png" alt="fix-4" width="42">
-<img src="src/all-badges/fix-commit/fix-5.png" alt="fix-5" width="42">
-<img src="src/all-badges/fix-commit/fix-6.png" alt="fix-6" width="42">
-<img src="src/all-badges/fix-commit/fix-6+.png" alt="fix-6+" width="42">
-<img src="src/all-badges/chore-commit/chore-commit.png" alt="chore-commit" width="42">
-<img src="src/all-badges/delorean/delorean.png" alt="delorean" width="42">
-<img src="src/all-badges/covid-19/covid-19.png" alt="covid-19" width="42">
-<img src="src/all-badges/pr-collaboration/pr-collaboration-5.png" alt="pr-collaboration-5" width="42">
-<img src="src/all-badges/pr-collaboration/pr-collaboration-10.png" alt="pr-collaboration-10" width="42">
-<img src="src/all-badges/pr-collaboration/pr-collaboration-15.png" alt="pr-collaboration-15" width="42">
-<img src="src/all-badges/pr-collaboration/pr-collaboration-20.png" alt="pr-collaboration-20" width="42">
-<img src="src/all-badges/pr-collaboration/pr-collaboration-25.png" alt="pr-collaboration-25" width="42">
-<img src="src/all-badges/public-keys/public-keys-1.png" alt="public-keys-1" width="42">
-<img src="src/all-badges/public-keys/public-keys-2.png" alt="public-keys-2" width="42">
-<img src="src/all-badges/public-keys/public-keys-3.png" alt="public-keys-3" width="42">
-<img src="src/all-badges/public-keys/public-keys-4.png" alt="public-keys-4" width="42">
-<img src="src/all-badges/public-keys/public-keys-5.png" alt="public-keys-5" width="42">
-<img src="src/all-badges/old-issue/old-issue-1.png" alt="old-issue-1" width="42">
-<img src="src/all-badges/old-issue/old-issue-2.png" alt="old-issue-2" width="42">
-<img src="src/all-badges/old-issue/old-issue-3.png" alt="old-issue-3" width="42">
-<img src="src/all-badges/old-issue/old-issue-4.png" alt="old-issue-4" width="42">
-<img src="src/all-badges/old-issue/old-issue-5.png" alt="old-issue-5" width="42">
-<img src="src/all-badges/old-issue/old-issue-6.png" alt="old-issue-6" width="42">
-<img src="src/all-badges/old-issue/old-issue-7.png" alt="old-issue-7" width="42">
-<img src="src/all-badges/old-issue/old-issue-8.png" alt="old-issue-8" width="42">
-<img src="src/all-badges/old-issue/old-issue-9.png" alt="old-issue-9" width="42">
-<img src="src/all-badges/old-issue/old-issue-10.png" alt="old-issue-10" width="42">
-<img src="src/all-badges/this-is-fine/this-is-fine.png" alt="this-is-fine" width="42">
-<img src="src/all-badges/the-ultimate-question/the-ultimate-question.png" alt="the-ultimate-question" width="42">
-<img src="src/all-badges/favorite-word/favorite-word.png" alt="favorite-word" width="42">
-<img src="src/all-badges/polite-coder/polite-coder.png" alt="polite-coder" width="42">
-<img src="src/all-badges/polite-coder/rebel-coder.png" alt="rebel-coder" width="42">
-<img src="src/all-badges/emoji-only-commit/emoji-only-commit.png" alt="emoji-only-commit" width="42">
-<img src="src/all-badges/spooky-commit/spooky-commit.png" alt="spooky-commit" width="42">
+<img src="badges/abc-commit/a-commit.png" alt="a-commit" width="64">
+<img src="badges/stars/stars-20000.png" alt="stars-20000" width="64">
+<img src="badges/time-of-commit/morning-commits.png" alt="morning-commits" width="64">
+<img src="badges/github-anniversary/github-anniversary-5.png" alt="github-anniversary-5" width="64">
+<img src="badges/dead-commit/dead-commit.png" alt="dead-commit" width="64">
+<img src="badges/bad-words/bad-words.png" alt="bad-words" width="64">
+<img src="badges/my-badges-contributor/my-badges-contributor.png" alt="my-badges-contributor" width="64">
+<img src="badges/delorean/delorean.png" alt="delorean" width="64">
+<img src="badges/covid-19/covid-19.png" alt="covid-19" width="64">
+<img src="badges/pr-collaboration/pr-collaboration-5.png" alt="pr-collaboration-5" width="64">
+<img src="badges/public-keys/public-keys-1.png" alt="public-keys-1" width="64">
+<img src="badges/old-issue/old-issue-1.png" alt="old-issue-1" width="64">
+<img src="badges/this-is-fine/this-is-fine.png" alt="this-is-fine" width="64">
+<img src="badges/the-ultimate-question/the-ultimate-question.png" alt="the-ultimate-question" width="64">
+<img src="badges/favorite-word/favorite-word.png" alt="favorite-word" width="64">
+<img src="badges/spooky-commit/spooky-commit.png" alt="spooky-commit" width="64">
+<img src="badges/cosmetic-commit/cosmetic-commit.png" alt="cosmetic-commit" width="64">
 </p>
 
 ## Installation
tsconfig.json
@@ -7,7 +7,12 @@
     "strict": true,
     "outDir": "./dist",
     "declaration": false,
-    "allowJs": true
+    "allowJs": true,
+    "paths": {
+      "#src": ["./src/index.ts"],
+      "#badges": ["./badges/index.ts"],
+      "#badges/*": ["./badges/*"]
+    }
   },
-  "include": ["./src/**/*"]
+  "include": ["./src/**/*", "./badges/**/*"]
 }