107 lines
3.2 KiB
JavaScript
107 lines
3.2 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',
|
|
'@add-ideas/toolbox-shell-react',
|
|
'@dnd-kit/accessibility',
|
|
'@dnd-kit/core',
|
|
'@dnd-kit/sortable',
|
|
'@dnd-kit/utilities',
|
|
'react',
|
|
'react-dom',
|
|
'scheduler',
|
|
'tslib',
|
|
];
|
|
|
|
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();
|