Commit c4be73a

Anton Golub <antongolub@antongolub.com>
2023-10-11 15:27:06
feat: introduce `omit` and `pick` options to filter badges on generation
1 parent 52bfa33
.github/workflows/test.yml
@@ -13,3 +13,5 @@ jobs:
         run: npm run build
       - name: Check all images exist
         run: npm run check-images
+      - name: Run unit tests
+        run: npm run test:unit
scripts/test.mjs
@@ -0,0 +1,15 @@
+#!/usr/bin/env node
+
+import glob from 'fast-glob'
+import { pathToFileURL } from 'node:url'
+
+const focused = process.argv.slice(2)
+const suites = focused.length
+  ? focused
+  : await glob('test/**/*.test.{ts,cjs,mjs}', {
+      cwd: process.cwd(),
+      absolute: true,
+      onlyFiles: true,
+    })
+
+await Promise.all(suites.map((suite) => import(pathToFileURL(suite))))
src/all-badges/index.ts
@@ -13,3 +13,5 @@ export const allBadges = [
   await import('./chore-commit/chore-commit.js'),
   await import('./delorean/delorean.js'),
 ] as const
+
+export const names = allBadges.flatMap(({ default: { badges } }) => badges)
src/badges.ts
@@ -29,11 +29,30 @@ export type Badge = {
   image: string
 }
 
-export function badgeCollection(badges: Badge[], baseUrl: URL) {
+const voidGrant = {
+  evidence() {},
+  evidenceCommits() {},
+  evidenceCommitsWithMessage() {},
+  evidencePRs() {},
+}
+
+export function badgeCollection(
+  badges: Badge[],
+  baseUrl: URL,
+  pickBadges: string[],
+  omitBadges: string[],
+) {
   const indexes = new Map(badges.map((x, i) => [x.id, i]))
   const baseDir = path.basename(path.dirname(fileURLToPath(baseUrl)))
 
   return function grant(id: ID, desc: string) {
+    if (!pickBadges.includes(id) || omitBadges.includes(id)) {
+      if (indexes.has(id)) {
+        badges.splice(indexes.get(id)!, 1)
+      }
+      return voidGrant
+    }
+
     const badge: Badge = {
       id,
       desc,
src/main.ts
@@ -6,7 +6,7 @@ import { Octokit, RequestError } from 'octokit'
 import { retry } from '@octokit/plugin-retry'
 import { throttling } from '@octokit/plugin-throttling'
 import { collect, Data } from './collect/collect.js'
-import { allBadges } from './all-badges/index.js'
+import { allBadges, names } from './all-badges/index.js'
 import { Badge, badgeCollection } from './badges.js'
 import { updateReadme } from './update-readme.js'
 import { updateBadges } from './update-badges.js'
@@ -14,7 +14,7 @@ import { updateBadges } from './update-badges.js'
 void (async function main() {
   const { env } = process
   const argv = minimist(process.argv.slice(2), {
-    string: ['data', 'repo', 'token', 'size', 'user'],
+    string: ['data', 'repo', 'token', 'size', 'user', 'pick', 'omit'],
     boolean: ['dryrun'],
   })
   const {
@@ -24,8 +24,12 @@ void (async function main() {
     data: dataPath = '',
     size,
     dryrun,
+    pick,
+    omit,
   } = argv
   const [owner, repo] = repository?.split('/', 2) || [username, username]
+  const pickBadges = pick ? pick.split(',') : names
+  const omitBadges = omit ? omit.split(',') : []
 
   const MyOctokit = Octokit.plugin(retry, throttling)
   const octokit = new MyOctokit({
@@ -96,7 +100,7 @@ void (async function main() {
   }
 
   for (const { default: presenter } of allBadges) {
-    const grant = badgeCollection(badges, presenter.url)
+    const grant = badgeCollection(badges, presenter.url, pickBadges, omitBadges)
     presenter.present(data, grant)
   }
   console.log('Badges', badges)
test/badges.test.ts
@@ -0,0 +1,71 @@
+import * as assert from 'node:assert'
+import { describe, it } from 'node:test'
+import { allBadges, names } from '../src/all-badges/index.js'
+import { Badge, badgeCollection } from '../src/badges.js'
+import abcPresenter from '../src/all-badges/abc-commit/abc-commit.js'
+
+describe('badges', () => {
+  it('exposes all badges presenters', () => {
+    const expected = [
+      'a-commit',
+      'ab-commit',
+      'abc-commit',
+      'abcd-commit',
+      'abcde-commit',
+      'abcdef-commit',
+      'stars-100',
+      'stars-500',
+      'stars-1000',
+      'stars-2000',
+      'stars-5000',
+      'stars-10000',
+      'stars-20000',
+      'midnight-commits',
+      'morning-commits',
+      'evening-commits',
+      'yeti',
+      'star-gazer',
+      'dead-commit',
+      'bad-words',
+      'mass-delete-commit',
+      'mass-delete-commit-10k',
+      'revert-revert-commit',
+      'my-badges-contributor',
+      'fix-2',
+      'fix-3',
+      'fix-4',
+      'fix-5',
+      'fix-6',
+      'fix-6+',
+    ]
+
+    assert.deepEqual(names, expected)
+  })
+
+  it('grant respects pick and omit params', () => {
+    const badges: Badge[] = []
+    const presenter = abcPresenter
+    const pickBadges = ['a-commit', 'ab-commit', 'abc-commit', 'abcd-commit']
+    const omitBadges = ['ab-commit', 'abc-commit']
+    const grant = badgeCollection(badges, presenter.url, pickBadges, omitBadges)
+
+    abcPresenter.badges.forEach((badge) => grant(badge, 'test'))
+
+    assert.deepEqual(badges, [
+      {
+        id: 'a-commit',
+        desc: 'test',
+        body: '',
+        image:
+          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/abc-commit/a-commit.png?raw=true',
+      },
+      {
+        id: 'abcd-commit',
+        desc: 'test',
+        body: '',
+        image:
+          'https://github.com/my-badges/my-badges/blob/master/src/all-badges/abc-commit/abcd-commit.png?raw=true',
+      },
+    ])
+  })
+})
.gitignore
@@ -2,3 +2,4 @@
 /data/
 /dist/
 yarn.lock
+coverage
package.json
@@ -14,7 +14,8 @@
     "build": "npm run copy-files && npm run tsc",
     "copy-files": "copyfiles -u 1 src/**/*.graphql dist/",
     "check-images": "node dist/check-images.js",
-    "test": "node test.js",
+    "test": "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",
     "release": "release-it"
   },
   "dependencies": {
@@ -27,8 +28,12 @@
     "@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",
     "prettier": "^3.0.3",
+    "release-it": "^16.2.1",
+    "ts-node": "^10.9.1",
     "typescript": "^5.2.2"
   },
   "files": [
README.md
@@ -92,14 +92,16 @@ jobs:
 - Start **my-badges** workflow, or wait for it to run automatically.
 
 ## Configuration
-| Param    | ENV alias      | Description                                                                              | Default       |
-|----------|----------------|------------------------------------------------------------------------------------------|---------------|
-| `token`  | `GITHUB_TOKEN` | Auth token                                                                               |               |
-| `user`   | `GITHUB_USER`  | Username                                                                                 |               |
-| `repo`   | `GITHUB_REPO`  | Repository name to push badges                                                           | `{user/user}` |
-| `data`   |                | Path to JSON to generate badges. If empty, required data will be obtained from the GH API |               |
-| `size`   |                | Badge size for README.md, px                                                             | 64            |
-| `dryrun` |                | Generate badges, but skip pushing to git                                                 |               |
+| Param    | ENV alias      | Description                                                                                                                                       | Default       |
+|----------|----------------|---------------------------------------------------------------------------------------------------------------------------------------------------|---------------|
+| `token`  | `GITHUB_TOKEN` | Auth token                                                                                                                                        |               |
+| `user`   | `GITHUB_USER`  | Username                                                                                                                                          |               |
+| `repo`   | `GITHUB_REPO`  | Repository name to push badges                                                                                                                    | `{user/user}` |
+| `data`   |                | Path to JSON to generate badges. If empty, required data will be obtained from the GH API                                                         |               |
+| `size`   |                | Badge size for README.md, px                                                                                                                      | 64            |
+| `dryrun` |                | Generate badges, but skip pushing to git                                                                                                          |               |
+| `pick`   |                | List of badges to pick. Pass `--pick="a-commit,ab-commit,revert-revert-commit"` to generate only the specified entries. If empty gets all of them |               |
+| `omit`   |                | List of badges to exclude. For example, if you're too shy to flex your stars: `--omit:stars-100,stars-500,stars-1000`                             |               |                                                                                                         
 
 ## Contributing badges
 
tsconfig.json
@@ -6,7 +6,8 @@
     "module": "NodeNext",
     "strict": true,
     "outDir": "./dist",
-    "declaration": false
+    "declaration": false,
+    "allowJs": true
   },
   "include": ["./src/**/*"]
 }