import assert from 'node:assert/strict' import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, symlinkSync, } from 'node:fs' import { join } from 'node:path' import test from 'node:test' import { fixture, REPO } from './fixtures.mjs' const SCRIPT = join(REPO, 'scripts/commit-staged.mjs') function commitFixture(t) { const f = fixture(t) f.git('init', '-q') f.git('config', 'user.name', 'Fixture') f.git('config', 'user.email', 'fixture@example.com') f.git('config', 'commit.gpgsign', 'false') f.write('old.md', 'old\n') f.git('add', '.') f.git('commit', '-qm', 'fixture') f.base = f.git('rev-parse', 'HEAD').trim() f.capture = join(f.root, 'payload.json') f.message = f.write('message', 'chore: archive fragments\n\nDetails.\n') f.nodeExecutable( 'bin/gh', `import { readFileSync, writeFileSync } from 'node:fs' writeFileSync(process.env.CAPTURE, readFileSync(0)) console.log(JSON.stringify(process.env.FAIL ? { errors: [{ message: 'head moved' }] } : { data: { createCommitOnBranch: { commit: { oid: 'signed-oid' } } } })) `, ) Object.assign(f.env, { PATH: `${join(f.root, 'bin')}:${process.env.PATH}`, GITHUB_REPOSITORY: 'Start9Labs/fixture', CAPTURE: f.capture, }) f.runScript = () => f.run('node', [SCRIPT, f.message, 'master', f.base], { encoding: 'utf8' }) f.payload = () => JSON.parse(readFileSync(f.capture, 'utf8')).variables.input return f } test('rename, large content and index ownership', t => { const f = commitFixture(t) f.git('mv', 'old.md', 'name with spaces.md') const contents = 'Large prose.\n'.repeat(20000) f.write('name with spaces.md', contents) f.git('add', 'name with spaces.md') f.write('name with spaces.md', 'unstaged content') const result = f.runScript() assert.equal(result.status, 0, result.stderr) const payload = f.payload() assert.equal(payload.expectedHeadOid, f.base) assert.deepEqual(payload.branch, { repositoryNameWithOwner: 'Start9Labs/fixture', branchName: 'master', }) assert.deepEqual(payload.message, { headline: 'chore: archive fragments', body: 'Details.', }) assert.deepEqual(payload.fileChanges.deletions, [{ path: 'old.md' }]) const [addition] = payload.fileChanges.additions assert.equal(payload.fileChanges.additions.length, 1) assert.equal(addition.path, 'name with spaces.md') assert.equal(Buffer.from(addition.contents, 'base64').toString(), contents) }) test('staged binary blob over one MiB', t => { const f = commitFixture(t) const contents = Buffer.alloc(256 * 8192) for (let i = 0; i < contents.length; i++) contents[i] = i % 256 f.write('large.bin', contents) f.git('add', 'large.bin') f.write('large.bin', Buffer.from('unstaged')) const result = f.runScript() assert.equal(result.status, 0, result.stderr) assert.equal(result.stdout, 'signed-oid\n') const additions = f.payload().fileChanges.additions assert.equal(additions[0].path, 'large.bin') assert.deepEqual(Buffer.from(additions[0].contents, 'base64'), contents) }) test('modes are not silently rewritten', t => { const f = commitFixture(t) for (const mode of ['symlink', 'executable']) { f.git('reset', '--hard', f.base) const path = join(f.root, 'unsupported') rmSync(path, { force: true }) if (mode === 'symlink') symlinkSync('old.md', path) else { f.write('unsupported', '#!/bin/sh\n') chmodSync(path, 0o755) } f.git('add', 'unsupported') assert.equal(f.runScript().status, 2, mode) assert.equal(existsSync(f.capture), false, mode) } }) test('deletion only', t => { const f = commitFixture(t) f.git('rm', 'old.md') const result = f.runScript() assert.equal(result.status, 0, result.stderr) assert.deepEqual(f.payload().fileChanges, { additions: [], deletions: [{ path: 'old.md' }], }) }) test('existing unsupported modes cannot be deleted or normalized', t => { const f = commitFixture(t) const originalBase = f.base for (const mode of ['symlink', 'executable', 'gitlink']) { for (const change of ['delete', 'normalize']) { f.git('reset', '--hard', originalBase) const path = join(f.root, 'unsupported') rmSync(path, { force: true }) if (mode === 'gitlink') { f.git( 'update-index', '--add', '--cacheinfo', `160000,${originalBase},unsupported`, ) } else { if (mode === 'symlink') symlinkSync('old.md', path) else { f.write('unsupported', '#!/bin/sh\n') chmodSync(path, 0o755) } f.git('add', 'unsupported') } f.git('commit', '-qm', 'unsupported mode') f.base = f.git('rev-parse', 'HEAD').trim() rmSync(path, { force: true }) f.git('update-index', '--force-remove', 'unsupported') if (change === 'normalize') { f.write('unsupported', 'ordinary file\n') f.git('add', 'unsupported') } const result = f.runScript() assert.equal(result.status, 2, `${mode}/${change}: ${result.stderr}`) assert.equal(existsSync(f.capture), false, `${mode}/${change}`) } } }) test('new gitlink is rejected', t => { const f = commitFixture(t) f.git('update-index', '--add', '--cacheinfo', `160000,${f.base},submodule`) const result = f.runScript() assert.equal(result.status, 2, result.stderr) assert.equal(existsSync(f.capture), false) }) test('GraphQL errors fail even with HTTP success', t => { const f = commitFixture(t) f.write('old.md', 'changed\n') f.git('add', 'old.md') f.env.FAIL = '1' const result = f.runScript() assert.notEqual(result.status, 0) assert.match(result.stderr, /head moved/) }) test('tag compiler workflow with local commit API', t => { const f = commitFixture(t) mkdirSync(join(f.root, 'scripts')) for (const name of [ 'changelog.mjs', 'changelog-version.mjs', 'commit-staged.mjs', 'docs-versions.mjs', ]) { copyFileSync(join(REPO, 'scripts', name), join(f.root, 'scripts', name)) } copyFileSync(join(REPO, '.prettierrc.json'), join(f.root, '.prettierrc.json')) const pin = JSON.parse(readFileSync(join(REPO, 'package.json'), 'utf8')) .devDependencies.prettier f.write( 'package.json', JSON.stringify({ devDependencies: { prettier: pin } }), ) f.write( 'projects/start-sdk/changelog/patch-fixed-released.md', '---\nrelease-notes: true\n---\n\n- Released fix.\n\n- Second released fix.\n', ) f.write( 'projects/start-sdk/changelog/patch-security-released.md', '---\nrelease-notes: true\n---\n\n- Released security fix.\n', ) f.write( 'projects/start-sdk/changelog/patch-security-released.pl_PL.md', '- Poprawka bezpieczeństwa.\n', ) f.write( 'projects/start-sdk/changelog/patch-fixed-second-released.md', '---\nrelease-notes: false\n---\n\n- Third released fix.\n', ) f.write( 'projects/start-sdk/changelog/patch-fixed-released.pl_PL.md', '- Poprawka.\n\n- Druga poprawka.\n', ) f.write( 'projects/start-sdk/release-notes/1.2.3.pl_PL.md', '## Nowości\n\n\n', ) f.write('projects/start-sdk/CHANGELOG.md', '# Changelog\n') f.git('add', 'scripts', 'projects', 'package.json', '.prettierrc.json') f.git('commit', '-qm', 'release') f.git('-c', 'tag.gpgsign=false', 'tag', 'start-sdk/v1.2.3') const catalog = 'shared-libs/crates/start-core/locales/release-notes.json' f.write(catalog, readFileSync(join(REPO, catalog))) f.git('add', 'shared-libs') f.git('commit', '-qm', 'compiler labels') const remote = join(f.root, 'origin.git') f.git('init', '--bare', '-q', remote) f.git('remote', 'add', 'origin', remote) f.git('push', '-q', 'origin', 'HEAD:master', 'HEAD:live-docs', '--tags') f.git('fetch', '-q', 'origin') f.git('checkout', '--detach', 'origin/live-docs') const mock = join(f.root, 'bin/gh.mjs') f.write( 'bin/gh.mjs', String.raw`#!/usr/bin/env node import assert from 'node:assert/strict' import { spawnSync } from 'node:child_process' import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' const payload = JSON.parse(readFileSync(0, 'utf8')).variables.input const remote = process.env.LOCAL_REMOTE const env = { ...process.env } const marker = join(process.env.RUNNER_TEMP, 'raced') const race = !existsSync(marker) if (race) { writeFileSync(marker, '') payload.fileChanges = { deletions: [], additions: [{ path: 'projects/start-sdk/changelog/patch-fixed-after-tag.md', contents: Buffer.from('---\nrelease-notes: false\n---\n\n- New work after the tag.\n').toString('base64'), }] } } function git(args, input) { const result = spawnSync('git', ['--git-dir', remote, ...args], { env, input, maxBuffer: Infinity }) assert.ifError(result.error) assert.equal(result.status, 0, result.stderr.toString()) return result.stdout.toString().trim() } const temp = mkdtempSync(join(tmpdir(), 'local-commit-api-')) let oid try { env.GIT_INDEX_FILE = join(temp, 'index') env.GIT_WORK_TREE = temp git(['read-tree', payload.expectedHeadOid]) for (const deletion of payload.fileChanges.deletions) { git(['update-index', '--force-remove', deletion.path]) } for (const addition of payload.fileChanges.additions) { const blob = git(['hash-object', '-w', '--stdin'], Buffer.from(addition.contents, 'base64')) git(['update-index', '--add', '--cacheinfo', '100644,' + blob + ',' + addition.path]) } const tree = git(['write-tree']) oid = git(['-c', 'user.name=Fixture', '-c', 'user.email=fixture@example.com', '-c', 'commit.gpgsign=false', 'commit-tree', tree, '-p', payload.expectedHeadOid], Buffer.from(payload.message.headline)) git(['update-ref', 'refs/heads/master', oid, payload.expectedHeadOid]) } finally { rmSync(temp, { recursive: true, force: true }) } console.log(JSON.stringify(race ? { errors: [{ message: 'head moved' }] } : { data: { createCommitOnBranch: { commit: { oid } } } })) `, ) chmodSync(mock, 0o755) const workflow = readFileSync( join(REPO, '.github/workflows/docs-sync-on-tag.yml'), 'utf8', ) const block = workflow .split(' - name: Compile released changelogs onto master\n')[1] .split(' run: |\n')[1] .split('\n - name:')[0] const shell = block .split('\n') .map(line => line.slice(10)) .join('\n') const runnerTemp = join(f.root, 'runner-temp') mkdirSync(runnerTemp) Object.assign(f.env, { LOCAL_REMOTE: remote, RUNNER_TEMP: runnerTemp, GITHUB_REF_NAME: 'start-sdk/v1.2.3', }) let result = f.run('bash', ['-c', shell], { encoding: 'utf8' }) assert.equal(result.status, 0, result.stdout + result.stderr) assert.equal(f.git('status', '--porcelain', '--untracked-files=no'), '') assert.equal( f.git('rev-parse', 'HEAD'), f.git('rev-parse', 'origin/live-docs'), ) const history = f.git('show', 'origin/master:projects/start-sdk/CHANGELOG.md') assert.equal( history, '# Changelog\n\n## [1.2.3]\n\n### Fixed\n\n- Released fix.\n\n- Second released fix.\n\n- Third released fix.\n\n### Security\n\n- Released security fix.\n', ) assert.ok( !f .git('ls-tree', '-r', '--name-only', 'origin/master', 'projects') .includes('patch-fixed-released.md'), ) assert.match( f.git( 'show', 'origin/master:projects/start-sdk/changelog/patch-fixed-after-tag.md', ), /New work after the tag\./, ) assert.ok(!history.includes('New work after the tag.')) const notesPath = 'projects/start-sdk/release-notes/1.2.3.md' const notes = f.git('show', `origin/master:${notesPath}`) assert.equal( notes, '## Highlights\n\n- Released fix.\n\n- Second released fix.\n\n## Security\n\n- Released security fix.\n', ) assert.match(result.stderr, /head moved/) const localizedPath = 'projects/start-sdk/release-notes/1.2.3.pl_PL.md' const localized = f.git('show', `origin/master:${localizedPath}`) assert.equal( localized, '## Nowości\n\n- Poprawka.\n\n- Druga poprawka.\n\n## Bezpieczeństwo\n\n- Poprawka bezpieczeństwa.\n', ) assert.ok(existsSync(join(runnerTemp, 'scripts/changelog.mjs'))) assert.deepEqual( readFileSync(join(runnerTemp, catalog)), readFileSync(join(REPO, catalog)), ) assert.equal( f.git('ls-tree', '-r', '--name-only', 'start-sdk/v1.2.3', '--', catalog), '', ) assert.ok( !f .git( 'ls-tree', '-r', '--name-only', 'origin/master', 'projects/start-sdk/changelog', ) .includes('.pl_PL.md'), ) const master = f.git('rev-parse', 'origin/master') result = f.run('bash', ['-c', shell], { encoding: 'utf8' }) assert.equal(result.status, 0, result.stdout + result.stderr) assert.equal(f.git('rev-parse', 'origin/master'), master) assert.equal( f.git('show', 'origin/master:projects/start-sdk/CHANGELOG.md'), history, ) assert.equal(f.git('show', `origin/master:${notesPath}`), notes) const flat = f.write( 'legacy-compiler/flat/changelog.mjs', readFileSync(join(REPO, 'scripts/changelog.mjs')), ) f.write( 'legacy-compiler/flat/changelog-version.mjs', readFileSync(join(REPO, 'scripts/changelog-version.mjs')), ) assert.ok(!existsSync(join(f.root, 'legacy-compiler', catalog))) const legacy = f.run( process.execPath, [ flat, 'notes', 'projects/start-sdk', '1.2.3', '--locale', 'pl_PL', '--ref', 'start-sdk/v1.2.3', ], { encoding: 'utf8' }, ) assert.equal(legacy.status, 0, legacy.stderr) assert.equal(legacy.stdout, localized) f.git('read-tree', '-u', '--reset', 'start-sdk/v1.2.3') f.env.PROJECT = 'start-sdk' const archive = workflow .slice( workflow.indexOf( ' git ls-tree -r --name-only origin/master -- projects/', ), workflow.indexOf(' git add -A'), ) .split('\n') .map(line => line.slice(10)) .join('\n') const copied = f.run('bash', ['-euo', 'pipefail', '-c', archive], { encoding: 'utf8', }) assert.equal(copied.status, 0, copied.stdout + copied.stderr) assert.equal(f.git('show', `:${notesPath}`), notes) assert.equal(f.git('show', `:${localizedPath}`), localized) }) test('empty index makes no request', t => { const f = commitFixture(t) assert.equal(f.runScript().status, 0) assert.equal(existsSync(f.capture), false) })