license-kit
Version:
Aggregate license notes of OSS libraries used in your Node.js project, analyze & visualize OSS licenses with AI-turbocharged tooling
139 lines (138 loc) • 8.24 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = analyzeCommandSetup;
const node_fs_1 = __importDefault(require("node:fs"));
const node_path_1 = __importDefault(require("node:path"));
const node_process_1 = __importDefault(require("node:process"));
const licenses_1 = require("@callstack/licenses");
const colorette_1 = require("colorette");
const table_1 = require("table");
const scanOptionsUtils_1 = require("../scanOptionsUtils");
const commandUtils_1 = require("../utils/commandUtils");
const tableConfig = {
border: (0, table_1.getBorderCharacters)('norc'),
};
const categoryToEmojiMapping = {
[licenses_1.LicenseCategory.PERMISSIVE]: '🔓',
[licenses_1.LicenseCategory.WEAK_COPYLEFT]: '🟡',
[licenses_1.LicenseCategory.STRONG_COPYLEFT]: '🔒',
[licenses_1.LicenseCategory.UNKNOWN]: '❓',
};
function getLicenseColor(license) {
if (license === 'unknown')
return (0, colorette_1.yellow)(license);
if (licenses_1.WEAK_COPYLEFT_LICENSES_LOWERCASE.has(license))
return (0, colorette_1.yellow)(license);
if (licenses_1.STRONG_COPYLEFT_LICENSES_LOWERCASE.has(license))
return (0, colorette_1.red)(license);
return license;
}
function analyzeCommandSetup(program) {
return (0, commandUtils_1.curryCommonScanOptions)(program
.command('analyze')
.description('Scan licenses & report the insights: summary, top license types, optionally unknowns & breakdown of licenses by different features.')
.option('--root [path]', 'Path to the root of your project', '.')
.option('--list-unknown', 'List unknown licenses', false)
.option('--show-breakdown', 'Show breakdown of licenses by category and type', false)).action((options) => {
const repoRootPath = node_path_1.default.resolve(node_process_1.default.cwd(), options.root);
const packageJsonPath = node_path_1.default.join(repoRootPath, 'package.json');
if (!node_fs_1.default.existsSync(packageJsonPath)) {
console.error(`package.json not found at ${packageJsonPath}`);
node_process_1.default.exit(1);
}
const { name = null } = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, 'utf8'));
if (name) {
console.log((0, colorette_1.underline)(`💼 Project: ${(0, colorette_1.whiteBright)(name)}`));
}
console.log();
const licenses = (0, licenses_1.scanDependencies)(packageJsonPath, (0, scanOptionsUtils_1.createScanOptionsFactory)(options));
const { byCategory, byLicense, categorizedLicenses, total, description, categoriesPresence } = (0, licenses_1.analyzeLicenses)(licenses);
console.log();
const summaryColor = categoriesPresence.hasAllPermissive
? colorette_1.green
: categoriesPresence.hasAnyUnknown
? colorette_1.yellowBright
: categoriesPresence.hasAnyStrongCopyleft
? colorette_1.red
: colorette_1.yellow;
console.log(`📦 ${(0, colorette_1.bold)('Total packages')}: ${(0, colorette_1.whiteBright)(total)}`);
console.log(`🔓 ${summaryColor('Graph state summary')}: ${description}`);
const byLicenseEntries = Object.entries(byLicense);
console.log(`🧮 ${(0, colorette_1.underline)('Top 5')} licenses in your project: ${byLicenseEntries
.sort(([, a], [, b]) => b - a)
.slice(0, 5)
.map(([license, count]) => `${(0, colorette_1.whiteBright)(license)} (${count})`)
.join(', ')} ${byLicenseEntries.length > 5
? (0, colorette_1.italic)((0, colorette_1.yellow)(` + ${byLicenseEntries.length - 5} more (${options.showBreakdown ? 'see below' : 'pass --show-breakdown to view'})`))
: ''}`);
console.log();
// breakdown of categories checklist
const unknownCount = byCategory[licenses_1.LicenseCategory.UNKNOWN];
const hasUnknown = unknownCount > 0;
const copyleftCount = byCategory[licenses_1.LicenseCategory.STRONG_COPYLEFT];
const hasCopyleft = copyleftCount > 0;
const weakCopyleftCount = byCategory[licenses_1.LicenseCategory.WEAK_COPYLEFT];
const hasWeakCopyleft = weakCopyleftCount > 0;
console.log(`${categoryToEmojiMapping[licenses_1.LicenseCategory.STRONG_COPYLEFT]} Copyleft licenses: ${(hasCopyleft ? colorette_1.red : colorette_1.green)(copyleftCount)} ${hasCopyleft ? '⚠️' : '✅'}`);
console.log(`${categoryToEmojiMapping[licenses_1.LicenseCategory.WEAK_COPYLEFT]} Weak copyleft licenses: ${(hasWeakCopyleft
? colorette_1.yellow
: colorette_1.green)(weakCopyleftCount)} ${hasWeakCopyleft ? '⚠️' : '✅'}`);
console.log(`${categoryToEmojiMapping[licenses_1.LicenseCategory.UNKNOWN]} Unknown licenses: ${(hasUnknown ? colorette_1.yellow : colorette_1.green)(unknownCount)} ${hasUnknown ? '⚠️' : '✅'}`);
console.log(`${categoryToEmojiMapping[licenses_1.LicenseCategory.PERMISSIVE]} Permissive licenses: ${(0, colorette_1.green)(byCategory[licenses_1.LicenseCategory.PERMISSIVE])}`);
if (options.listUnknown) {
console.log();
console.log('―'.repeat(node_process_1.default.stdout.columns));
console.log();
console.log(`🔍 ${(0, colorette_1.whiteBright)('Unknown licenses')}`);
console.log((0, table_1.table)(Object.entries(licenses)
.filter(([_packageKey, license]) => (0, licenses_1.categorizeLicense)(license.type) === licenses_1.LicenseCategory.UNKNOWN)
.map(([packageKey]) => [packageKey]), tableConfig));
}
if (options.showBreakdown) {
console.log();
console.log('―'.repeat(node_process_1.default.stdout.columns));
console.log();
// licenses by category
const byCategoryTable = [['Category', 'Count', 'Percentage']];
Object.entries(byCategory).forEach(([category, count]) => {
byCategoryTable.push([category, count, Number(((count / total) * 100).toFixed(2))]);
});
console.log(`🗂️ Licenses by ${(0, colorette_1.whiteBright)('category')}`);
console.log((0, table_1.table)(byCategoryTable, tableConfig));
console.log();
// licenses by type
const byLicenseTable = [['License', 'Count', 'Percentage']];
byLicenseEntries
.sort(([, a], [, b]) => b - a)
.forEach(([license, count]) => {
byLicenseTable.push([
license === 'unknown' ? (0, colorette_1.yellow)(license) : getLicenseColor(license),
count,
Number(((count / total) * 100).toFixed(2)),
]);
});
console.log(`🏷️ Licenses by ${(0, colorette_1.whiteBright)('type')}`);
console.log((0, table_1.table)(byLicenseTable, tableConfig));
console.log();
const nonFullyPermissiveObj = [
['License', 'Category'],
...Object.entries(categorizedLicenses)
.filter(([_license, category]) => category !== licenses_1.LicenseCategory.PERMISSIVE)
.map(([license, category]) => [license, `${category} ${categoryToEmojiMapping[category]}`]),
];
// non-permissive licenses
if (nonFullyPermissiveObj.length > 0) {
console.log(`⚠️ ${(0, colorette_1.whiteBright)('Non-fully-permissive')} licenses`);
console.log((0, table_1.table)(nonFullyPermissiveObj, tableConfig));
}
else {
console.log(`✅ ${(0, colorette_1.whiteBright)('All licenses are fully-permissive')}`);
}
}
console.log();
console.log((0, colorette_1.italic)('Remember that all data presented by the tool require manual verification. The presented information may be inaccurate or incomplete.'));
});
}