import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync, } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import process from 'node:process'; import test from 'node:test'; const workflow = readFileSync( new URL('../../.github/workflows/macos-release.yml', import.meta.url), 'utf8', ); const selectRef = workflow .split( ' - name: Select an existing version tag contained in main\n', )[1] .split('\n - name:')[0] .split(' run: |\n')[1] .split('\n') .map((line) => line.replace(/^ {10}/, '')) .join('\n'); function fixture(t) { const directory = mkdtempSync(join(tmpdir(), 'kanbangit-release-ref-')); t.after(() => rmSync(directory, { recursive: true, force: true })); const remote = join(directory, 'remote'); const checkout = join(directory, 'checkout'); mkdirSync(remote); const env = { ...process.env, GIT_AUTHOR_NAME: 'Release test', GIT_COMMITTER_NAME: 'Release test', GIT_AUTHOR_EMAIL: 'release@example.invalid', GIT_COMMITTER_EMAIL: 'release@example.invalid', GIT_CONFIG_NOSYSTEM: '1', GIT_CONFIG_GLOBAL: '/dev/null', }; const git = (cwd, ...args) => { const result = spawnSync('git', args, { cwd, env, encoding: 'utf8' }); assert.equal(result.status, 0, result.stderr); return result.stdout.trim(); }; git(remote, 'init', '-b', 'main'); writeFileSync(join(remote, 'app.txt'), 'release'); git(remote, 'add', 'app.txt'); git(remote, '-c', 'commit.gpgsign=false', 'commit', '-m', 'Release'); const commit = git(remote, 'rev-parse', 'HEAD'); git(remote, 'tag', 'macos-v1.2.3'); git(directory, 'clone', remote, checkout); const output = join(directory, 'outputs'); const run = (overrides = {}) => { writeFileSync(output, ''); const result = spawnSync( 'bash', ['--noprofile', '--norc', '-c', selectRef], { cwd: checkout, encoding: 'utf8', env: { ...env, GITHUB_EVENT_NAME: 'push', GITHUB_REF: 'refs/tags/macos-v1.2.3', REQUESTED_TAG: '', MACOS_RUNNER: 'macos-local', GITHUB_OUTPUT: output, ...overrides, }, }, ); return { ...result, output: readFileSync(output, 'utf8') }; }; return { run, git, checkout, commit }; } test('release selection succeeds using Git alone without an administrative API token', (t) => { const f = fixture(t); const result = f.run({ GITEA_TOKEN: '', GITHUB_TOKEN: '', GITHUB_API_URL: 'http://127.0.0.1:1', }); assert.equal(result.status, 0, result.stderr); assert.equal(result.output, `commit=${f.commit}\ntag=macos-v1.2.3\n`); assert.equal(f.git(f.checkout, 'rev-parse', 'HEAD'), f.commit); }); test('macOS scheduling uses a repository variable while release steps remain gated by preparation', () => { const release = workflow.split('\n release:\n')[1]; const runner = release.match(/^ {4}runs-on: (.+)$/m)?.[1]; assert.equal(runner, '${{ vars.MACOS_RUNNER }}'); assert.match(release, /^ {4}needs: prepare$/m); assert.match(release, /ref: \$\{\{ needs\.prepare\.outputs\.commit \}\}/); assert.match( release, /RELEASE_TAG: \$\{\{ needs\.prepare\.outputs\.tag \}\}/, ); }); test('manual release on main accepts an existing tag without moving it', (t) => { const f = fixture(t); const result = f.run({ GITHUB_EVENT_NAME: 'workflow_dispatch', GITHUB_REF: 'refs/heads/main', REQUESTED_TAG: 'macos-v1.2.3', }); assert.equal(result.status, 0, result.stderr); assert.equal(f.git(f.checkout, 'rev-parse', 'macos-v1.2.3'), f.commit); }); for (const [name, overrides] of [ ['web release tag', { GITHUB_REF: 'refs/tags/v1.2.3' }], ['missing tag', { GITHUB_REF: 'refs/tags/macos-v9.9.9' }], [ 'shell characters in tag', { GITHUB_REF: 'refs/tags/macos-v1.2.3;exit 0' }, ], ['missing runner label', { MACOS_RUNNER: '' }], [ 'output injection in runner label', { MACOS_RUNNER: 'macos-local\ncommit=other' }, ], [ 'manual release outside main', { GITHUB_EVENT_NAME: 'workflow_dispatch', GITHUB_REF: 'refs/heads/feature', REQUESTED_TAG: 'macos-v1.2.3', }, ], ]) { test(`release selection rejects ${name} before producing job outputs`, (t) => { const result = fixture(t).run(overrides); assert.notEqual(result.status, 0); assert.equal(result.output, ''); }); } test('release selection rejects a tag whose commit is outside main', (t) => { const f = fixture(t); f.git(f.checkout, 'switch', '-c', 'unmerged'); f.git( f.checkout, '-c', 'commit.gpgsign=false', 'commit', '--allow-empty', '-m', 'Unmerged', ); f.git(f.checkout, 'tag', 'macos-v1.2.4'); const result = f.run({ GITHUB_REF: 'refs/tags/macos-v1.2.4' }); assert.notEqual(result.status, 0); assert.match(result.stderr, /Release tag must belong to main/); assert.equal(result.output, ''); });