Commit 75734fe

Anton Medvedev <anton@medv.io>
2023-10-16 11:57:41
Refactor to use git instead of gh api
1 parent a7335f7
src/constants.ts
@@ -1,1 +0,0 @@
-export const MY_BADGES_JSON_PATH = 'my-badges/my-badges.json'
src/get-badges.ts
@@ -1,100 +0,0 @@
-import { collect, Data } from './collect/collect.js'
-import fs from 'node:fs'
-import { Badge } from './badges.js'
-import { Octokit, RequestError } from 'octokit'
-import { decodeBase64 } from './utils.js'
-import { MY_BADGES_JSON_PATH } from './constants.js'
-
-export const getData = async (
-  octokit: Octokit,
-  dataPath: string,
-  username: string,
-): Promise<Data> => {
-  if (dataPath !== '') {
-    if (!fs.existsSync(dataPath)) {
-      throw new Error('Data file not found')
-    }
-    return JSON.parse(fs.readFileSync(dataPath, 'utf8')) as Data
-  }
-
-  if (!username) {
-    throw new Error('Specify username')
-  }
-
-  const data = await collect(octokit, username)
-  if (!fs.existsSync('data')) {
-    fs.mkdirSync('data')
-  }
-  fs.writeFileSync(`data/${username}.json`, JSON.stringify(data, null, 2))
-
-  return data
-}
-
-export const getOldData = async (
-  octokit: Octokit,
-  owner: string,
-  repo: string,
-  myBadges?: any, // test snapshot
-) => {
-  console.log('Loading my-badges.json')
-  try {
-    const { data } =
-      myBadges ||
-      (await octokit.request<'content-file'>(
-        'GET /repos/{owner}/{repo}/contents/{path}',
-        {
-          owner,
-          repo,
-          path: MY_BADGES_JSON_PATH,
-        },
-      ))
-    const oldJson = decodeBase64(data.content)
-    const jsonSha = data.sha
-    const userBadges = JSON.parse(oldJson)
-
-    // Add missing tier property in old my-badges.json.
-    for (const b of userBadges) {
-      if (b.tier === undefined) b.tier = 0
-    }
-
-    return {
-      userBadges,
-      jsonSha,
-      oldJson,
-    }
-  } catch (err) {
-    console.log(err)
-    if (err instanceof RequestError && err.response?.status != 404) {
-      throw err
-    }
-  }
-
-  return {}
-}
-
-export const getBadges = async (
-  octokit: Octokit,
-  dataPath: string,
-  username: string,
-  owner: string,
-  repo: string,
-): Promise<{
-  data: Data
-  userBadges: Badge[]
-  oldJson?: string
-  jsonSha?: string
-}> => {
-  const data = await getData(octokit, dataPath, username)
-  const {
-    oldJson = undefined,
-    jsonSha = undefined,
-    userBadges = [],
-  } = owner && repo ? await getOldData(octokit, owner, repo) : {}
-
-  return {
-    data,
-    oldJson,
-    jsonSha,
-    userBadges,
-  }
-}
src/get-data.ts
@@ -0,0 +1,31 @@
+import { collect, Data } from './collect/collect.js'
+import fs from 'node:fs'
+import { Badge } from './badges.js'
+import { Octokit, RequestError } from 'octokit'
+import { decodeBase64 } from './utils.js'
+import { MY_BADGES_JSON_PATH } from './constants.js'
+
+export async function getData(
+  octokit: Octokit,
+  dataPath: string,
+  username: string,
+): Promise<Data> {
+  if (dataPath !== '') {
+    if (!fs.existsSync(dataPath)) {
+      throw new Error('Data file not found')
+    }
+    return JSON.parse(fs.readFileSync(dataPath, 'utf8')) as Data
+  }
+
+  if (!username) {
+    throw new Error('Specify username')
+  }
+
+  const data = await collect(octokit, username)
+  if (!fs.existsSync('data')) {
+    fs.mkdirSync('data')
+  }
+  fs.writeFileSync(`data/${username}.json`, JSON.stringify(data, null, 2))
+
+  return data
+}
src/main.ts
@@ -4,11 +4,12 @@ import minimist from 'minimist'
 import { Octokit } from 'octokit'
 import { retry } from '@octokit/plugin-retry'
 import { throttling } from '@octokit/plugin-throttling'
-import { updateReadme } from './update-readme.js'
-import { updateBadges } from './update-badges.js'
 import { presentBadges } from './present-badges.js'
-import { getBadges } from './get-badges.js'
+import { getData } from './get-data.js'
 import { allBadges } from './all-badges/index.js'
+import { getUserBadges, gitClone, gitPush } from './repo.js'
+import { updateBadges } from './update-badges.js'
+import { updateReadme } from './update-readme.js'
 
 void (async function main() {
   try {
@@ -28,7 +29,8 @@ void (async function main() {
       omit,
       compact,
     } = argv
-    const [owner, repo] = repository?.split('/', 2) || [username, username]
+    const [owner, repo]: [string | undefined, string | undefined] =
+      repository?.split('/', 2) || [username, username]
     const pickBadges = pick ? pick.split(',') : []
     const omitBadges = omit ? omit.split(',') : []
 
@@ -55,14 +57,10 @@ void (async function main() {
       retry: { doNotRetry: ['429'] },
     })
 
-    let { userBadges, data, oldJson, jsonSha } = await getBadges(
-      octokit,
-      dataPath,
-      username,
-      owner,
-      repo,
-    )
+    if (owner && repo) gitClone(owner, repo)
+    const data = await getData(octokit, dataPath, username)
 
+    let userBadges = getUserBadges()
     userBadges = presentBadges(
       allBadges.map((m) => m.default),
       data,
@@ -75,16 +73,11 @@ void (async function main() {
     console.log(JSON.stringify(userBadges, null, 2))
 
     if (owner && repo) {
-      await updateBadges(
-        octokit,
-        owner,
-        repo,
-        userBadges,
-        oldJson,
-        jsonSha,
-        dryrun,
-      )
-      await updateReadme(octokit, owner, repo, userBadges, size, dryrun)
+      updateBadges(userBadges)
+      updateReadme(userBadges, size)
+      if (!dryrun) {
+        gitPush()
+      }
     }
   } catch (e) {
     console.error(e)
src/repo.ts
@@ -0,0 +1,43 @@
+import fs from 'node:fs'
+import { spawnSync } from 'node:child_process'
+import { chdir } from 'node:process'
+import { Badge } from './badges.js'
+
+export function gitClone(owner: string, repo: string) {
+  spawnSync(
+    'git',
+    ['clone', '--depth=1', `git@github.com:${owner}/${repo}.git`, 'repo'],
+    {
+      stdio: 'inherit',
+    },
+  )
+
+  chdir('repo')
+
+  spawnSync('git', ['config', 'user.name', 'My Badges'], { stdio: 'inherit' })
+  spawnSync(
+    'git',
+    ['config', 'user.email', 'my-badges@users.noreply.github.com'],
+    { stdio: 'inherit' },
+  )
+
+  chdir('..')
+}
+
+export function gitPush() {
+  chdir('repo')
+
+  spawnSync('git', ['add', '.'], { stdio: 'inherit' })
+  spawnSync('git', ['commit', '-m', 'Update badges'], { stdio: 'inherit' })
+  spawnSync('git', ['push'], { stdio: 'inherit' })
+
+  chdir('..')
+}
+
+export function getUserBadges(): Badge[] {
+  if (!fs.existsSync('my-badges/my-badges.json')) {
+    return []
+  }
+  const data = fs.readFileSync('my-badges/my-badges.json', 'utf8')
+  return JSON.parse(data)
+}
src/update-badges.ts
@@ -1,60 +1,17 @@
-import { Octokit, RequestError } from 'octokit'
+import fs from 'node:fs'
+import { chdir } from 'node:process'
 import { Badge } from './badges.js'
-import { quoteAttr, upload } from './utils.js'
-import { MY_BADGES_JSON_PATH } from './constants.js'
+import { quoteAttr } from './utils.js'
 
-export async function updateBadges(
-  octokit: Octokit,
-  owner: string,
-  repo: string,
-  badges: Badge[],
-  oldJson: string | undefined,
-  jsonSha: string | undefined,
-  dryrun: boolean,
-) {
-  const newJson = JSON.stringify(badges, null, 2)
-  if (newJson == oldJson) {
-    console.log('No change in my-badges.json')
-  } else {
-    await upload(
-      octokit,
-      'PUT /repos/{owner}/{repo}/contents/{path}',
-      {
-        owner,
-        repo,
-        path: MY_BADGES_JSON_PATH,
-        message: 'Update my-badges',
-        committer: {
-          name: 'My Badges',
-          email: 'my-badges@github.com',
-        },
-        content: newJson,
-        sha: jsonSha,
-      },
-      dryrun,
-    )
-  }
+export function updateBadges(badges: Badge[]) {
+  chdir('repo')
+
+  fs.mkdirSync('my-badges', { recursive: true })
+  fs.writeFileSync('my-badges/my-badges.json', JSON.stringify(badges, null, 2))
 
   for (const badge of badges) {
     const badgePath = `my-badges/${badge.id}.md`
 
-    let sha: string | undefined
-    let oldContent: string | undefined
-
-    try {
-      console.log(`Loading ${badgePath}`)
-      const resp = await octokit.request<'readme'>(
-        'GET /repos/{owner}/{repo}/contents/{path}',
-        { owner, repo, path: badgePath },
-      )
-      sha = resp.data.sha
-      oldContent = Buffer.from(resp.data.content, 'base64').toString('utf8')
-    } catch (err) {
-      if (err instanceof RequestError && err.response?.status != 404) {
-        throw err
-      }
-    }
-
     const desc = quoteAttr(badge.desc)
     const content =
       `<img src="${badge.image}" alt="${desc}" title="${desc}" width="128">\n` +
@@ -64,27 +21,8 @@ export async function updateBadges(
       `\n\n\n` +
       `Created by <a href="https://github.com/my-badges/my-badges">My Badges</a>`
 
-    if (content === oldContent) {
-      console.log(`No change in ${badgePath}`)
-      continue
-    }
-
-    await upload(
-      octokit,
-      'PUT /repos/{owner}/{repo}/contents/{path}',
-      {
-        owner,
-        repo,
-        path: badgePath,
-        message: sha ? `Update ${badge.id}.md` : `Add ${badge.id}.md`,
-        committer: {
-          name: 'My Badges',
-          email: 'my-badges@github.com',
-        },
-        content,
-        sha: sha,
-      },
-      dryrun,
-    )
+    fs.writeFileSync(badgePath, content)
   }
+
+  chdir('..')
 }
src/update-readme.ts
@@ -1,17 +1,35 @@
-import { Octokit } from 'octokit'
+import fs from 'node:fs'
+import { chdir } from 'node:process'
 import { Badge } from './badges.js'
-import { quoteAttr, upload } from './utils.js'
-import { allBadges } from './all-badges/index.js'
+import { quoteAttr } from './utils.js'
+
+export function updateReadme(badges: Badge[], size: number | string) {
+  chdir('repo')
+
+  const readmeFilename = detectReadmeFilename()
+  const readmeContent = fs.readFileSync(readmeFilename, 'utf8')
+
+  const content = generateReadme(readmeContent, badges, size)
+  fs.writeFileSync(readmeFilename, content)
+
+  chdir('..')
+}
+
+function detectReadmeFilename(): string {
+  if (fs.existsSync('README.md')) return 'README.md'
+  if (fs.existsSync('readme.md')) return 'readme.md'
+  throw new Error('Cannot find README.md')
+}
 
 export function generateReadme(
-  readme: string,
+  readmeContent: string,
   badges: Badge[],
   size: number | string = 64,
 ) {
   const startString = '<!-- my-badges start -->'
   const endString = '<!-- my-badges end -->'
 
-  let content = readme
+  let content = readmeContent
 
   const start = content.indexOf(startString)
   const end = content.indexOf(endString)
@@ -40,44 +58,3 @@ export function generateReadme(
 
   return content
 }
-
-export async function updateReadme(
-  octokit: Octokit,
-  owner: string,
-  repo: string,
-  badges: Badge[],
-  size: number | string,
-  dryrun: boolean,
-) {
-  const readme = await octokit.request<'readme'>(
-    'GET /repos/{owner}/{repo}/readme',
-    {
-      owner,
-      repo,
-    },
-  )
-
-  const content = generateReadme(
-    Buffer.from(readme.data.content, 'base64').toString('utf8'),
-    badges,
-    size,
-  )
-
-  await upload(
-    octokit,
-    'PUT /repos/{owner}/{repo}/contents/{path}',
-    {
-      owner,
-      repo,
-      path: readme.data.path,
-      message: 'Update my-badges',
-      committer: {
-        name: 'My Badges',
-        email: 'my-badges@github.com',
-      },
-      content,
-      sha: readme.data.sha,
-    },
-    dryrun,
-  )
-}
test/get-badges.test.ts
@@ -4,7 +4,7 @@ import fs from 'node:fs'
 import path from 'node:path'
 import os from 'node:os'
 import { Octokit } from 'octokit'
-import { getBadges, getOldData } from '../src/get-badges.js'
+import { getBadges, getOldData } from '../src/get-data.js'
 import { encodeBase64 } from '../src/utils.js'
 
 const tempy = () => fs.mkdtempSync(path.join(os.tmpdir(), 'tempy-'))
.gitignore
@@ -2,5 +2,6 @@
 /data/
 /dist/
 /my-badges/
+/repo/
 yarn.lock
 coverage
package-lock.json
@@ -1,12 +1,12 @@
 {
   "name": "update-my-badges",
-  "version": "1.0.106",
+  "version": "1.0.105",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "update-my-badges",
-      "version": "1.0.106",
+      "version": "1.0.105",
       "license": "MIT",
       "dependencies": {
         "@octokit/plugin-retry": "^6.0.1",