import assert from 'node:assert/strict'; import { execFileSync, spawnSync } from 'node:child_process'; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; const directory = mkdtempSync(join(tmpdir(), 'ci-policy-')); const script = fileURLToPath(new URL('./ci-policy.mjs', import.meta.url)); const git = (...args) => execFileSync('git', args, { cwd: directory, encoding: 'utf8' }).trim(); const check = (variables = {}, expected = 0) => { const commit = git('rev-parse', 'HEAD'); const branch = variables.branch ?? 'feature/test'; const eventName = variables.eventName ?? 'push'; const ref = variables.tag ? `refs/tags/${variables.tag}` : eventName === 'pull_request' ? 'refs/pull/42/head' : `refs/heads/${branch}`; const event = { ref, before: variables.before }; if (eventName === 'pull_request') { event.pull_request = { title: variables.title ?? 'feat(test): add policy checks', head: { ref: branch, sha: variables.headSha ?? commit, repo: { full_name: variables.headRepository ?? 'nessar/vac-optimizer' }, }, base: { ref: variables.target ?? 'develop' }, }; } const eventPath = join(directory, 'event.json'); const outputPath = join(directory, 'outputs'); writeFileSync(eventPath, JSON.stringify(event)); writeFileSync(outputPath, ''); const result = spawnSync(process.execPath, [script], { cwd: directory, encoding: 'utf8', env: { PATH: process.env.PATH, GITHUB_EVENT_NAME: eventName, GITHUB_EVENT_PATH: eventPath, GITHUB_REF: ref, GITHUB_SHA: variables.sha ?? commit, GITHUB_REPOSITORY: 'nessar/vac-optimizer', GITHUB_OUTPUT: outputPath, }, }); assert.equal(result.status, expected, result.stdout + result.stderr); return readFileSync(outputPath, 'utf8'); }; const pr = { eventName: 'pull_request' }; try { git('init', '--initial-branch=main'); git('config', 'user.name', 'CI policy test'); git('config', 'user.email', 'ci-policy@example.invalid'); git('remote', 'add', 'origin', directory); writeFileSync(join(directory, 'package.json'), '{"version":"1.5.0"}\n'); git('add', 'package.json'); git('commit', '-m', 'Legacy history is allowed'); git('branch', 'develop'); git('switch', '-c', 'feature/test'); git('commit', '--allow-empty', '-m', 'feat(test)!: add checks'); check({}); check(pr); check({ ...pr, headRepository: 'contributor/vac-optimizer' }); check({ eventName: 'workflow_dispatch' }); assert.match(check({ branch: 'Feature/My_calendar' }), /slug=feature-my-calendar\n/); check({ ...pr, headSha: '0'.repeat(40) }, 1); check({ eventName: 'pull_request_target' }, 1); check({ ...pr, title: 'Draft: feat: test policy' }); check({ ...pr, title: '(Draft) feat: test policy' }); check({ ...pr, target: 'main' }, 1); check({ ...pr, title: 'Add checks' }, 1); check({ tag: 'v1.5.0' }, 1); check({ sha: '0'.repeat(40) }, 1); git('switch', 'develop'); git('commit', '--allow-empty', '-m', 'fix: another change'); git('switch', 'feature/test'); check(pr, 1); git('rebase', 'develop'); check(pr); const before = git('rev-parse', 'HEAD'); git('commit', '--allow-empty', '-m', 'not conventional'); check({ before: before }, 1); check(pr, 1); git('commit', '--amend', '--allow-empty', '-m', 'ci: fix the commit title'); check({ before: before }); git('switch', 'develop'); git('merge', '--no-ff', 'feature/test', '-m', 'feat(test): add policy checks'); git('switch', '-c', 'release/1.6.0'); writeFileSync(join(directory, 'package.json'), '{"version":"1.6.0"}\n'); git('add', 'package.json'); git('commit', '-m', 'chore(release): prepare 1.6.0'); check({ ...pr, branch: 'release/1.6.0', target: 'main', }); check({ ...pr, branch: 'release/1.6.0' }); git('switch', 'main'); git('merge', '--no-ff', 'release/1.6.0', '-m', 'chore(release): publish 1.6.0'); check({ tag: 'v1.6.0' }); check({ tag: 'v1.5.0' }, 1); check({ tag: 'v01.6.0' }, 1); check({ tag: 'v1.6.0-rc.1' }, 1); check({ ...pr, branch: 'main' }); git('switch', '-c', 'hotfix/1.6.1'); git('commit', '--allow-empty', '-m', 'fix: production issue'); check({ ...pr, branch: 'hotfix/1.6.1', target: 'main', }); check({ ...pr, branch: 'hotfix/1.6.1' }); // The working branch must not pass by merging develop instead of rebasing. git('switch', 'feature/test'); git('commit', '--allow-empty', '-m', 'feat: keep working'); git('merge', '--no-ff', 'develop', '-m', 'chore: merge develop'); check(pr, 1); console.log('CI policy integration checks passed.'); } finally { rmSync(directory, { recursive: true, force: true }); }