apisurf
Version:
Analyze API surface changes between npm package versions to catch breaking changes
104 lines (103 loc) • 4.31 kB
JavaScript
import * as fs from 'fs';
import { compareApiSurfaces } from '../analyzers/compareApiSurfaces.js';
import { calculateSemverImpact } from './calculateSemverImpact.js';
/**
* Writes a detailed JSON log of API surface changes between two versions.
* Includes full export information, type definitions, and change analysis.
*/
export async function writeApiSurfaceLog(logfilePath, base, head, packageName, fromVersion, toVersion, format) {
// Create a fake package object for diff comparison
const pkg = { name: packageName, path: `npm:${packageName}` };
const diff = compareApiSurfaces(base, head, pkg);
const semverImpact = calculateSemverImpact([diff]);
// Build diffs array with semver impact
const diffs = [];
// Add breaking changes
for (const change of diff.breakingChanges) {
diffs.push({
type: 'breaking',
changeType: change.type,
description: change.description,
before: change.before,
after: change.after,
semverImpact: 'major'
});
}
// Add non-breaking changes
for (const change of diff.nonBreakingChanges) {
diffs.push({
type: 'non-breaking',
changeType: change.type,
description: change.description,
details: change.details,
semverImpact: 'minor'
});
}
// Helper function to create export details with typing info
const createExportDetails = (exports, typeOnlyExports, typeDefinitions) => {
const result = {};
for (const exportName of exports) {
result[exportName] = {
type: 'value',
typeInfo: typeDefinitions?.get(exportName) ? {
kind: typeDefinitions.get(exportName).kind,
signature: typeDefinitions.get(exportName).signature,
returnType: typeDefinitions.get(exportName).returnType,
parameters: typeDefinitions.get(exportName).parameters
} : undefined
};
}
for (const exportName of typeOnlyExports) {
result[exportName] = {
type: 'type-only',
typeInfo: typeDefinitions?.get(exportName) ? {
kind: typeDefinitions.get(exportName).kind,
signature: typeDefinitions.get(exportName).signature,
properties: typeDefinitions.get(exportName).properties ? Object.fromEntries(typeDefinitions.get(exportName).properties) : undefined
} : undefined
};
}
return result;
};
const logData = {
packageName,
fromVersion,
toVersion,
timestamp: new Date().toISOString(),
semverImpact: semverImpact.minimumBump,
semverReason: semverImpact.reason,
diffs,
from: {
packageName: base.packageName,
version: base.version,
exports: createExportDetails(base.namedExports, base.typeOnlyExports, base.typeDefinitions),
defaultExport: base.defaultExport,
starExports: base.starExports,
summary: {
namedExportsCount: base.namedExports.size,
typeOnlyExportsCount: base.typeOnlyExports.size,
hasDefaultExport: base.defaultExport,
starExportsCount: base.starExports.length
}
},
to: {
packageName: head.packageName,
version: head.version,
exports: createExportDetails(head.namedExports, head.typeOnlyExports, head.typeDefinitions),
defaultExport: head.defaultExport,
starExports: head.starExports,
summary: {
namedExportsCount: head.namedExports.size,
typeOnlyExportsCount: head.typeOnlyExports.size,
hasDefaultExport: head.defaultExport,
starExportsCount: head.starExports.length
}
}
};
const logContent = JSON.stringify(logData, null, 2);
fs.writeFileSync(logfilePath, logContent, 'utf8');
// Only log to console when format is 'console' or not specified
if (!format || format === 'console') {
console.log(`API surface log written to: ${logfilePath}`);
}
}