You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
132 lines
3.5 KiB
132 lines
3.5 KiB
#!/usr/bin/env node
|
|
|
|
/**
|
|
* API Changes Check Script
|
|
*
|
|
* Checks for unintended API changes and blocks release if API changed
|
|
* without proper commit type (feat/breaking).
|
|
*
|
|
* @author Matthew Raymer
|
|
* @version 1.0.0
|
|
*/
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
const { execSync } = require('child_process');
|
|
|
|
const API_CHECKSUM_FILE = path.join(__dirname, '..', 'api-checksum.txt');
|
|
const DIST_TYPES_DIR = path.join(__dirname, '..', 'dist', 'esm');
|
|
|
|
/**
|
|
* Generate checksum of API definitions
|
|
* @returns {string} Checksum of API files
|
|
*/
|
|
function generateApiChecksum() {
|
|
try {
|
|
// Get all .d.ts files in dist/esm
|
|
const typeFiles = execSync(`find "${DIST_TYPES_DIR}" -name "*.d.ts" -type f`, { encoding: 'utf8' })
|
|
.trim()
|
|
.split('\n')
|
|
.filter(file => file.length > 0);
|
|
|
|
if (typeFiles.length === 0) {
|
|
console.warn('⚠️ No TypeScript definition files found in dist/esm');
|
|
return '';
|
|
}
|
|
|
|
// Generate checksum of all type files
|
|
const checksum = execSync(`cat ${typeFiles.join(' ')} | shasum -a 256`, { encoding: 'utf8' })
|
|
.trim()
|
|
.split(' ')[0];
|
|
|
|
return checksum;
|
|
} catch (error) {
|
|
console.error('Error generating API checksum:', error.message);
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get last commit message
|
|
* @returns {string} Last commit message
|
|
*/
|
|
function getLastCommitMessage() {
|
|
try {
|
|
return execSync('git log -1 --pretty=%B', { encoding: 'utf8' }).trim();
|
|
} catch (error) {
|
|
console.error('Error getting last commit message:', error.message);
|
|
return '';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if commit message indicates API change
|
|
* @param {string} commitMessage - Commit message
|
|
* @returns {boolean} True if API change is allowed
|
|
*/
|
|
function isApiChangeAllowed(commitMessage) {
|
|
const allowedPatterns = [
|
|
/^feat\(/i,
|
|
/^fix\(/i,
|
|
/BREAKING CHANGE/i,
|
|
/^feat!/i,
|
|
/^fix!/i
|
|
];
|
|
|
|
return allowedPatterns.some(pattern => pattern.test(commitMessage));
|
|
}
|
|
|
|
/**
|
|
* Check API changes and enforce rules
|
|
*/
|
|
function checkApiChanges() {
|
|
console.log('🔍 Checking API changes...');
|
|
|
|
if (!fs.existsSync(DIST_TYPES_DIR)) {
|
|
console.error('❌ Dist types directory not found. Run "npm run build" first.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const currentChecksum = generateApiChecksum();
|
|
|
|
if (!currentChecksum) {
|
|
console.error('❌ Could not generate API checksum');
|
|
process.exit(1);
|
|
}
|
|
|
|
let previousChecksum = '';
|
|
if (fs.existsSync(API_CHECKSUM_FILE)) {
|
|
previousChecksum = fs.readFileSync(API_CHECKSUM_FILE, 'utf8').trim();
|
|
}
|
|
|
|
if (previousChecksum === currentChecksum) {
|
|
console.log('✅ No API changes detected');
|
|
return;
|
|
}
|
|
|
|
console.log('⚠️ API changes detected');
|
|
console.log(`Previous: ${previousChecksum.substring(0, 8)}...`);
|
|
console.log(`Current: ${currentChecksum.substring(0, 8)}...`);
|
|
|
|
const commitMessage = getLastCommitMessage();
|
|
console.log(`Last commit: ${commitMessage}`);
|
|
|
|
if (!isApiChangeAllowed(commitMessage)) {
|
|
console.error('\n❌ API changes detected without proper commit type!');
|
|
console.error('🚫 Release blocked. API changes require:');
|
|
console.error(' - feat(scope): description');
|
|
console.error(' - fix(scope): description');
|
|
console.error(' - BREAKING CHANGE in commit message');
|
|
console.error(' - feat!(scope): or fix!(scope): for breaking changes');
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('✅ API changes allowed by commit type');
|
|
|
|
// Update checksum file
|
|
fs.writeFileSync(API_CHECKSUM_FILE, currentChecksum);
|
|
console.log('📝 Updated API checksum file');
|
|
}
|
|
|
|
// Run the check
|
|
checkApiChanges();
|
|
|