OverviewFilesCommitsIssuesDocsReleasesPeers

pear-git-v3 / test / ci-keep-releases.test.js

77 lines · javascript ref:
// A CI build that publishes a release collects that repo's older ones: newest
// N (default 2) stay, channel-pinned versions stay.
//
// Why: `gc-releases` existed but nothing ran it. On tabertron 2026-10-11 the
// release dir held 453 stale versions across 16 repos (11 GB -> 2 GB once
// collected by hand); hyper-tps alone had 138.

import test from 'brittle'
import { mkdtempSync, mkdirSync, writeFileSync } from '#node:fs'
import { join } from '#node:path'
import { tmpdir } from '#node:os'
import { execSync } from '../lib/_compat-exec.js'

import { ReleaseManager } from '../lib/release-manager.js'
import { buildAndPublish } from '../lib/ci-orchestrator.js'

function setup () {
  const base = mkdtempSync(join(tmpdir(), 'pgv3-ci-keep-'))
  const bare = join(base, 'app.git')
  const work = join(base, 'work')
  execSync(`git init -q --bare --initial-branch=main ${bare}`)
  execSync(`git init -q --initial-branch=main ${work}`)
  const g = (args) => execSync(`git -C ${work} ${args}`, { stdio: 'ignore' })
  g('config user.email ci@test')
  g('config user.name CI')
  mkdirSync(join(work, '.pear-git'), { recursive: true })
  writeFileSync(join(work, '.pear-git', 'ci.yaml'), `
steps:
  - name: build
    run: git_sha=$GIT_COMMIT_SHORT; echo "$git_sha" > built.txt
artifacts:
  - built.txt
channels:
  experimental:
    branches: ['main']
`)
  const commitAndPush = (n) => {
    writeFileSync(join(work, 'n.txt'), String(n))
    g('add -A')
    g(`commit -q -m c${n}`)
    g(`push -q ${bare} main`)
  }
  const rm = new ReleaseManager({
    releaseDir: join(base, 'releases'),
    deploy: { async rescan () {} },
    eventLog: { writable: true, async append () {} }
  })
  const sync = { info: () => ({ path: bare }) }
  return { rm, sync, commitAndPush }
}

test('each CI publish keeps only the newest 2 releases of that repo', async (t) => {
  const { rm, sync, commitAndPush } = setup()
  const built = []
  for (let i = 1; i <= 4; i++) {
    commitAndPush(i)
    const r = await buildAndPublish({ sync, releaseManager: rm, repo: 'app', ref: 'main' })
    t.ok(r.ok, 'build ' + i + ' ok')
    built.push(r.version)
  }
  const left = (await rm.list('app')).versions.map((v) => v.version).sort()
  t.alike(left, built.slice(2).sort(), 'the two newest remain')
})

test('a channel-pinned old version survives', async (t) => {
  const { rm, sync, commitAndPush } = setup()
  commitAndPush(1)
  const first = await buildAndPublish({ sync, releaseManager: rm, repo: 'app', ref: 'main', channel: 'production' })
  for (let i = 2; i <= 4; i++) {
    commitAndPush(i)
    await buildAndPublish({ sync, releaseManager: rm, repo: 'app', ref: 'main' })
  }
  const left = (await rm.list('app')).versions.map((v) => v.version)
  t.ok(left.includes(first.version), 'pinned production version kept')
  t.is(left.length, 3, 'the production pin + the newest two builds')
})