import { readFile, readdir, writeFile } from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const LICENSE_FILE = /^(?:licen[cs]e|copying|notice)(?:[._-].*)?$/iu; const RUNTIME_PACKAGES = [ '@add-ideas/toolbox-contract', 'react', 'react-dom', 'scheduler', ]; function compareCodePoints(left, right) { return left < right ? -1 : left > right ? 1 : 0; } export async function createRuntimeLicenseBundle( projectRoot, packageNames = RUNTIME_PACKAGES ) { const sections = []; for (const packageName of [...new Set(packageNames)].sort( compareCodePoints )) { const packageDirectory = path.join( projectRoot, 'node_modules', ...packageName.split('/') ); const packageJson = JSON.parse( await readFile(path.join(packageDirectory, 'package.json'), 'utf8') ); const licenseFiles = await collectLicenseFiles(packageDirectory); if (licenseFiles.length === 0) throw new Error(`No license text found for ${packageName}.`); const contents = []; for (const licenseFile of licenseFiles) { const text = await readFile( path.join(packageDirectory, ...licenseFile.split('/')), 'utf8' ); contents.push( `--- ${licenseFile} ---\n${text.replaceAll('\r\n', '\n').trim()}\n` ); } sections.push( [ '================================================================================', `${packageJson.name ?? packageName}@${packageJson.version ?? 'unknown'}`, `Declared license: ${String(packageJson.license ?? 'unspecified')}`, '================================================================================', ...contents, ].join('\n') ); } return [ 'Third-party software license texts bundled with add·ideas Toolbox', 'Generated deterministically from the installed runtime packages.', '', ...sections, '', ].join('\n'); } async function collectLicenseFiles(directory, prefix = '') { const matches = []; const entries = await readdir(directory, { withFileTypes: true }); entries.sort((left, right) => compareCodePoints(left.name, right.name)); for (const entry of entries) { if (entry.name === 'node_modules') continue; const relativePath = path.posix.join(prefix, entry.name); const absolutePath = path.join(directory, entry.name); if (entry.isDirectory()) matches.push(...(await collectLicenseFiles(absolutePath, relativePath))); else if (entry.isFile() && LICENSE_FILE.test(entry.name)) matches.push(relativePath); } return matches; } async function main() { const projectRoot = path.resolve( path.dirname(fileURLToPath(import.meta.url)), '..' ); const output = path.join(projectRoot, 'public', 'THIRD_PARTY_LICENSES.txt'); await writeFile( output, await createRuntimeLicenseBundle(projectRoot), 'utf8' ); process.stdout.write('Generated public/THIRD_PARTY_LICENSES.txt\n'); } if ( process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) ) await main();