scripts/update-packaging.mjs could not be shown to write zeros: run against the real cli-v1.0.0 SHA256SUMS from the all-zero placeholders at1bc4dddit fills correct values (reproduced in a clean worktree), and branch head8fc630dalready carried them. The old script was fragile, though: it read lines with split/reverse (a `*file` binary-mode name or a stray column would silently mis-key), never checked that a sum was 64 hex, accepted all-zero sums, did not check that any placeholder had actually matched, and wrote the formula before it knew the manifest would succeed. Now it parses `shasum -a 256` lines strictly, requires every target to be 64 lowercase hex and not all zeros, requires at least one url/sha256 pair and no unfilled sha256 left in the formula, computes both files first and writes only if everything passed, otherwise exits 1 with "nothing was written". Tested in scripts/update-packaging.test.mjs (fixture SHA256SUMS; run by `npm test` via `test:scripts`). Docs (packaging README, release runbook step 7, tap README) say to compare the diff with SHA256SUMS and that tap/bucket CI catches a wrong or zero sum only when its PR is opened and read. Ran against /Volumes/HD-2/Git.Local/QRun-IO/.worktrees/qqq-cli-release/dist/SHA256SUMS; the packaging files are unchanged from8fc630d, and each sum was checked by hand against it: darwin-arm64 5017a32ac5031d9146d541381099a959e5c19bbf04c48f725045d89d6007b211 darwin-x64 eb9b1d43d24b639118d94b175c343080a39ae9ba64c24645c1792ff3b16a37f1 linux-arm64 25f513bdda13bbe40cf536844eebc1e3a7e21cf7804024f25ebe7c42793d7ebc linux-x64 804d06cb7c0012c2f185ae5d94c6bb8f9e2482a440bb3c165361f1a14c941e41 windows-x64 cf418d7842a227cee412171878228004f9bc5098a272c64b89512d1e2d06a176 (linux musl sums are in SHA256SUMS but not used by the formula or manifest.) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
74 lines
4.0 KiB
JavaScript
74 lines
4.0 KiB
JavaScript
/*
|
|
* Copyright 2026 QRun.IO, Inc.
|
|
* Licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
*/
|
|
|
|
/** Tests for scripts/update-packaging.mjs. Run: node --test scripts/update-packaging.test.mjs */
|
|
|
|
import assert from 'node:assert/strict'
|
|
import { spawnSync } from 'node:child_process'
|
|
import { cpSync, mkdtempSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join } from 'node:path'
|
|
import { test } from 'node:test'
|
|
import { parseSums, updatePackaging } from './update-packaging.mjs'
|
|
|
|
const ZERO = '0'.repeat(64)
|
|
const h = (c) => c.repeat(64)
|
|
const FILES = ['qqq-darwin-arm64', 'qqq-darwin-x64', 'qqq-linux-arm64', 'qqq-linux-x64']
|
|
const sumsText = (omit = []) =>
|
|
[...FILES, 'qqq-linux-x64-musl', 'qqq-windows-x64.exe'].filter((f) => !omit.includes(f)).map((f, i) => `${'abcdef0123456'[i].repeat(64)} ${f}`).join('\n') + '\n'
|
|
const formula = `class Qqq < Formula
|
|
version "0.0.0"
|
|
${FILES.map((f) => ` url "https://github.com/QRun-IO/qqq-cli/releases/download/cli-v#{version}/${f}"\n sha256 "${ZERO}"`).join('\n')}
|
|
end
|
|
`
|
|
const manifest = JSON.stringify({ version: '0.0.0', architecture: { '64bit': { url: 'x', hash: ZERO } } }, null, 4) + '\n'
|
|
|
|
test('fills every sum from shasum output and never leaves zeros', () => {
|
|
const out = updatePackaging({ version: '1.0.0', sums: parseSums(sumsText()), formula, manifest })
|
|
assert.equal(out.formulaCount, 4)
|
|
assert.ok(!out.formula.includes(ZERO) && !out.manifest.includes(ZERO))
|
|
assert.match(out.formula, /version "1\.0\.0"/)
|
|
assert.ok(out.formula.includes(`qqq-darwin-arm64"\n sha256 "${h('a')}"`))
|
|
assert.equal(JSON.parse(out.manifest).architecture['64bit'].hash, h('f'))
|
|
})
|
|
|
|
test('a missing target throws', () => {
|
|
assert.throws(() => updatePackaging({ version: '1.0.0', sums: parseSums(sumsText(['qqq-linux-x64'])), formula, manifest }), /no qqq-linux-x64/)
|
|
assert.throws(() => updatePackaging({ version: '1.0.0', sums: parseSums(sumsText(['qqq-windows-x64.exe'])), formula, manifest }), /no qqq-windows-x64\.exe/)
|
|
})
|
|
|
|
test('malformed, short, uppercase-free or zero sums throw', () => {
|
|
for (const bad of ['abc', ZERO, 'g'.repeat(64)]) {
|
|
const sums = parseSums(sumsText().replace(h('a'), bad))
|
|
assert.throws(() => updatePackaging({ version: '1.0.0', sums, formula, manifest }), /malformed|all-zero/)
|
|
}
|
|
assert.throws(() => parseSums('only-one-column\n'), /unparseable/)
|
|
})
|
|
|
|
test('a formula with nothing to fill throws', () => {
|
|
assert.throws(() => updatePackaging({ version: '1.0.0', sums: parseSums(sumsText()), formula: 'class Qqq < Formula\nend\n', manifest }), /no url\/sha256/)
|
|
})
|
|
|
|
test('the CLI exits 1 and writes nothing when a sum is missing', () => {
|
|
const dir = mkdtempSync(join(tmpdir(), 'update-packaging-'))
|
|
mkdirSync(join(dir, 'scripts'))
|
|
cpSync(join(import.meta.dirname, 'update-packaging.mjs'), join(dir, 'scripts/update-packaging.mjs'))
|
|
mkdirSync(join(dir, 'packages/cli'), { recursive: true })
|
|
writeFileSync(join(dir, 'packages/cli/package.json'), '{"version":"1.0.0"}')
|
|
mkdirSync(join(dir, 'docs/packaging/homebrew-tap/Formula'), { recursive: true })
|
|
writeFileSync(join(dir, 'docs/packaging/homebrew-tap/Formula/qqq.rb'), formula)
|
|
writeFileSync(join(dir, 'docs/packaging/qqq.json'), manifest)
|
|
writeFileSync(join(dir, 'SHA256SUMS'), sumsText(['qqq-linux-arm64']))
|
|
const run = spawnSync(process.execPath, [join(dir, 'scripts/update-packaging.mjs'), join(dir, 'SHA256SUMS')], { encoding: 'utf8' })
|
|
assert.equal(run.status, 1)
|
|
assert.match(run.stderr, /nothing was written/)
|
|
assert.equal(readFileSync(join(dir, 'docs/packaging/homebrew-tap/Formula/qqq.rb'), 'utf8'), formula)
|
|
assert.equal(readFileSync(join(dir, 'docs/packaging/qqq.json'), 'utf8'), manifest)
|
|
writeFileSync(join(dir, 'SHA256SUMS'), sumsText())
|
|
const ok = spawnSync(process.execPath, [join(dir, 'scripts/update-packaging.mjs'), join(dir, 'SHA256SUMS')], { encoding: 'utf8' })
|
|
assert.equal(ok.status, 0, ok.stderr)
|
|
assert.ok(!readFileSync(join(dir, 'docs/packaging/qqq.json'), 'utf8').includes(ZERO))
|
|
})
|