87 lines
4.7 KiB
JavaScript
87 lines
4.7 KiB
JavaScript
// Exercise the actual release command in disposable repositories, never the workspace Git.
|
|
const assert = require('node:assert/strict');
|
|
const { execFileSync, spawnSync } = require('node:child_process');
|
|
const fs = require('node:fs');
|
|
const os = require('node:os');
|
|
const path = require('node:path');
|
|
const source = path.resolve(process.argv[2] || __dirname);
|
|
const cwd = fs.mkdtempSync(path.join(os.tmpdir(), 'wealthledger-release-'));
|
|
const env = { ...process.env, HUSKY: '0', NO_COLOR: '1' };
|
|
const git = (...args) => execFileSync('git', args, { cwd, env, encoding: 'utf8' }).trim();
|
|
const read = file => fs.readFileSync(path.join(cwd, file), 'utf8');
|
|
const write = (file, text) => fs.writeFileSync(path.join(cwd, file), text);
|
|
const prepare = (...args) => spawnSync(process.execPath,
|
|
[path.join(source, 'node_modules/release-it/bin/release-it.js'), '--ci', ...args],
|
|
{ cwd, env, encoding: 'utf8' });
|
|
const mustPass = result => assert.equal(result.status, 0, result.stdout + result.stderr);
|
|
const mustFail = result => assert.notEqual(result.status, 0, result.stdout + result.stderr);
|
|
const commit = message => git('commit', '--allow-empty', '-qm', message);
|
|
try {
|
|
fs.mkdirSync(path.join(cwd, 'scripts'));
|
|
fs.symlinkSync(path.join(source, 'node_modules'), path.join(cwd, 'node_modules'), 'dir');
|
|
for (const file of ['.release-it.json', 'scripts/check-release.cjs', 'commitlint.config.cjs']) {
|
|
fs.copyFileSync(path.join(source, file), path.join(cwd, file));
|
|
}
|
|
write('.gitignore', 'node_modules/\nremote.git/\n');
|
|
write('CHANGELOG.md', '# Changelog\n');
|
|
write('package.json', JSON.stringify({ name: 'release-fixture', private: true, version: '0.0.0' }));
|
|
write('package-lock.json', JSON.stringify({ name: 'release-fixture', version: '0.0.0', lockfileVersion: 3, packages: { '': { name: 'release-fixture', version: '0.0.0' } } }));
|
|
git('init', '-q', '-b', 'main');
|
|
git('config', 'user.name', 'Release test');
|
|
git('config', 'user.email', 'release@example.invalid');
|
|
git('config', 'core.hooksPath', '/dev/null');
|
|
git('init', '--bare', '-q', 'remote.git');
|
|
git('remote', 'add', 'origin', path.join(cwd, 'remote.git'));
|
|
git('add', '.');
|
|
commit('chore: initialize fixture');
|
|
mustFail(prepare('0.1.0')); // main cannot prepare releases.
|
|
git('switch', '-qc', 'release/test');
|
|
write('untracked.txt', 'must block\n');
|
|
mustFail(prepare('0.1.0'));
|
|
fs.unlinkSync(path.join(cwd, 'untracked.txt'));
|
|
const before = ['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read);
|
|
const head = git('rev-parse', 'HEAD');
|
|
mustPass(prepare('0.1.0', '--dry-run'));
|
|
assert.deepEqual(['package.json', 'package-lock.json', 'CHANGELOG.md'].map(read), before);
|
|
assert.equal(git('rev-parse', 'HEAD'), head);
|
|
mustPass(prepare('0.1.0'));
|
|
assert.equal(git('tag', '--list'), '', 'Preparation must not create tags');
|
|
assert.equal(git('log', '-1', '--format=%s'), 'chore(release): 0.1.0');
|
|
mustFail(prepare()); // Prepared version cannot be bumped again before tagging.
|
|
git('tag', '-a', 'v0.1.0', '-m', 'Release 0.1.0');
|
|
mustFail(prepare()); // No new commits.
|
|
for (const [message, expected] of [
|
|
['fix: correct calculation', '0.1.1'],
|
|
['feat: add export', '0.2.0'],
|
|
['docs: explain export', '0.2.1'],
|
|
['spec: define export', '0.2.2'],
|
|
['chore(deps): update dependencies', '0.2.3'],
|
|
['fix!: remove legacy contract', '1.0.0'],
|
|
['feat: change export\n\nBREAKING CHANGE: replace export format', '2.0.0'],
|
|
]) {
|
|
commit(message);
|
|
mustPass(prepare());
|
|
const pkg = JSON.parse(read('package.json'));
|
|
const lock = JSON.parse(read('package-lock.json'));
|
|
assert.equal(pkg.version, expected);
|
|
assert.equal(lock.version, expected);
|
|
assert.equal(lock.packages[''].version, expected);
|
|
assert.ok(read('CHANGELOG.md').includes(expected));
|
|
assert.equal(git('status', '--porcelain'), '');
|
|
git('tag', '-a', `v${expected}`, '-m', `Release ${expected}`);
|
|
}
|
|
assert.ok(read('CHANGELOG.md').includes('/commit/'), read('CHANGELOG.md'));
|
|
assert.ok(read('CHANGELOG.md').includes('/compare/v0.1.0...v0.1.1'));
|
|
assert.ok(!read('CHANGELOG.md').includes('chore(release)'));
|
|
assert.equal(read('CHANGELOG.md').split('correct calculation').length, 2, 'Prior changes must not be repeated');
|
|
assert.equal(spawnSync('git', ['--git-dir=remote.git', 'show-ref'], { cwd }).status, 1, 'Preparation must not push refs');
|
|
const lint = message => spawnSync(process.execPath,
|
|
[path.join(source, 'node_modules/@commitlint/cli/cli.js'), '--strict'],
|
|
{ cwd, env, input: message, encoding: 'utf8' });
|
|
mustPass(lint('spec: define contract'));
|
|
mustFail(lint('unstructured message'));
|
|
console.log(`PASS: release preparation, guards, version rules, lockfile and changelog (${source})`);
|
|
} finally {
|
|
fs.rmSync(cwd, { recursive: true, force: true });
|
|
}
|