Files
toolbox-portal/scripts/licenses.mjs

101 lines
3.0 KiB
JavaScript

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();