Files
qqq-cli/scripts/update-packaging.test.mjs
james.maesandClaude Opus 5.5 9890fb1300
node / node (push) Failing after 45s
build / node (push) Failing after 45s
release: Gitea release pipeline on v<CLI version> tags; 1.0.1-rc.1
.gitea/workflows/release.yaml replaces the orb publish and the GitHub release:
the scripts/release.mjs gate, Linux + darwin-x64 binaries in the container,
darwin-arm64 and windows-x64 built and smoke-tested on the native runners, a
draft Gitea release, SHA256SUMS checked against the published downloads, the
four packages to the QRun-IO Gitea npm registry (an existing version is
skipped, each package's own version picks next/latest), and for final versions
the Homebrew tap and Scoop bucket (pushed when PACKAGES_TOKEN can write them,
else the filled files are attached to the release).

release.mjs accepts its tag when it points at HEAD and prints the new path;
install.sh, update-packaging, the formula and the manifest use Gitea
downloads under v<version>; publishConfig points at the Gitea registry;
build.yaml now runs on branches only; the runbook describes the new path.
Versions: cli and core 1.0.1-rc.1, client 1.8.1-rc.1 (content changed since
1.0.0), conformance 1.6.0 unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-08 07:37:03 -06:00

75 lines
4.1 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://git.kof22.com/QRun-IO/qqq-cli/releases/download/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'))
assert.equal(JSON.parse(out.manifest).architecture['64bit'].url, 'https://git.kof22.com/QRun-IO/qqq-cli/releases/download/v1.0.0/qqq-windows-x64.exe')
})
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))
})