UNPKG

apisurf

Version:

Analyze API surface changes between npm package versions to catch breaking changes

104 lines (103 loc) 4.31 kB
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}`); } }