Files
Desktop/scripts/generate-acknowledgments.mjs
T
2026-04-02 16:30:34 -07:00

175 lines
5.0 KiB
JavaScript

// Copyright 2020 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
// @ts-check
import assert from 'node:assert';
import fs from 'node:fs';
import { join } from 'node:path';
import pMap from 'p-map';
import prettier from 'prettier';
import packageJson from '../package.json' with { type: 'json' };
// During development, you might use local versions of dependencies which are missing
// acknowledgment files. In this case we'll skip rebuilding the acknowledgment files.
// Enable this flag to throw an error.
const REQUIRE_SIGNAL_LIB_FILES = Boolean(process.env.REQUIRE_SIGNAL_LIB_FILES);
const { dependencies, devDependencies, optionalDependencies } = packageJson;
const SIGNAL_LIBS = [
'@signalapp/libsignal-client',
'@signalapp/ringrtc',
'@signalapp/mute-state-change',
];
const SKIPPED_DEPENDENCIES = new Set(SIGNAL_LIBS);
const rootDir = join(import.meta.dirname, '..');
const nodeModulesPath = join(rootDir, 'node_modules');
const destinationPath = join(rootDir, 'ACKNOWLEDGMENTS.md');
/**
* @param {string} fileName
*/
function isLicenseFileName(fileName) {
return /^licen[s|c]e/i.test(fileName);
}
/**
* @param {string} dependencyName
*/
async function getMarkdownForDependency(dependencyName) {
let licenseBody;
// fs-xattr is an optional dependency that may fail to install (on Windows, most
// commonly), so we have a special case for it here. We may need to do something
// similar for new optionalDependencies in the future.
if (dependencyName === 'fs-xattr' || dependencyName === 'growing-file') {
licenseBody = 'License: MIT';
} else {
const dependencyRootPath = join(nodeModulesPath, dependencyName);
const licenseFileName = (
await fs.promises.readdir(dependencyRootPath)
).find(isLicenseFileName);
if (licenseFileName) {
const licenseFilePath = join(dependencyRootPath, licenseFileName);
licenseBody = (
await fs.promises.readFile(licenseFilePath, 'utf8')
).trim();
} else {
const packageJsonPath = join(dependencyRootPath, 'package.json');
const { license } = JSON.parse(
await fs.promises.readFile(packageJsonPath, 'utf8')
);
if (!license) {
throw new Error(`Could not find license for ${dependencyName}`);
}
licenseBody = `License: ${license}`;
}
}
return [
`## ${dependencyName}`,
'',
...licenseBody.split(/\r?\n/).map(line => {
const trimmed = line.trim();
if (trimmed) {
return ` ${trimmed}`;
}
return trimmed;
}),
].join('\n');
}
/**
* @param {string} dependencyName
*/
async function getMarkdownForSignalLib(dependencyName) {
const dependencyRootPath = join(nodeModulesPath, dependencyName);
const licenseFilePath = join(
dependencyRootPath,
'dist',
'acknowledgments.md'
);
/** @type {string} */
let licenseBody;
try {
licenseBody = await fs.promises.readFile(licenseFilePath, 'utf8');
} catch (err) {
if (err.code === 'ENOENT' && !REQUIRE_SIGNAL_LIB_FILES) {
console.warn(
`Missing acknowledgments file for ${dependencyName}. Skipping generation of acknowledgments.`
);
process.exit(0);
}
throw err;
}
return [
`# Acknowledgements for ${dependencyName}`,
'',
licenseBody.replace(/^# Acknowledgments/, '').trim(),
].join('\n');
}
assert.deepStrictEqual(
Object.keys(optionalDependencies),
['fs-xattr'],
'Unexpected optionalDependencies when generating acknowledgments file. To ensure that this file is generated deterministically, make sure to special-case it the acknowledgments generation script.'
);
const dependencyNames = [
...Object.keys(dependencies),
...Object.keys(devDependencies),
...Object.keys(optionalDependencies),
]
.filter(name => !SKIPPED_DEPENDENCIES.has(name))
.sort();
const markdownsForDependency = await pMap(
dependencyNames,
getMarkdownForDependency,
// Without this, we may run into "too many open files" errors.
{
concurrency: 100,
signal: AbortSignal.timeout(1000 * 60 * 2),
}
);
// For our libraries copy the respective acknowledgement lists
const markdownsFromSignalLibs = await pMap(
SIGNAL_LIBS,
getMarkdownForSignalLib,
{
concurrency: 100,
signal: AbortSignal.timeout(1000 * 60 * 2),
}
);
const unformattedOutput = [
'<!-- Copyright 2020 Signal Messenger, LLC -->',
'<!-- SPDX-License-Identifier: AGPL-3.0-only -->',
'# Acknowledgments',
'',
'Signal Desktop makes use of the following open source projects.',
'',
markdownsForDependency.join('\n\n'),
'',
'## Kyber Patent License',
'',
'<https://csrc.nist.gov/csrc/media/Projects/post-quantum-cryptography/documents/selected-algos-2022/nist-pqc-license-summary-and-excerpts.pdf>',
'',
markdownsFromSignalLibs.join('\n\n'),
].join('\n');
const prettierConfig = await prettier.resolveConfig(destinationPath);
const output = await prettier.format(unformattedOutput, {
...prettierConfig,
filepath: destinationPath,
});
await fs.promises.writeFile(destinationPath, output);