feat: the build ships its dependency licenses and its commit
The minifier strips every legal comment, so the bundle reached users without the notice MIT and Apache-2.0 require to travel with a copy. collect-licenses quotes all 97 bundled packages into public/licenses.txt, which deploys like the font and dictionary licenses do. The About dialog's version now carries the short commit, so a user of the hosted app can identify the source it was built from - what AGPL 13 asks for and a fixed 0.1.0 could not answer.
This commit is contained in:
1 parent
946b7e44be
commit
f1b63c722c
5 files changed
+3755
-6
No files matched your search
@@ -0,0 +1,80 @@
|
||||
// Regenerates public/licenses.txt: the license text of every bundled dependency.
|
||||
// The minifier strips legal comments, and MIT/Apache-2.0 both require the notice
|
||||
// to reach whoever receives the code — so it ships beside the build instead.
|
||||
import { readdir, readFile, writeFile } from 'node:fs/promises';
|
||||
|
||||
const LICENSE_FILE = /^(LICENSE|LICENCE|COPYING)(\.\w+)?$/i;
|
||||
|
||||
// Read from disk, never through require: an export map that hides package.json
|
||||
// would silently drop the package (TipTap, odf-kit and docx all do).
|
||||
const manifest = async (name) => {
|
||||
try {
|
||||
return JSON.parse(await readFile(`node_modules/${name}/package.json`, 'utf8'));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const { dependencies } = JSON.parse(await readFile('package.json', 'utf8'));
|
||||
// Svelte is a devDependency but compiles its runtime into the bundle; the rest of
|
||||
// the dev tooling (Vite, TypeScript, vitest) never reaches a user.
|
||||
const roots = [...Object.keys(dependencies), 'svelte'];
|
||||
|
||||
// A dependency's own dependencies are bundled too, so walk the whole tree.
|
||||
async function resolveTree(names, seen = new Set()) {
|
||||
for (const name of names) {
|
||||
if (seen.has(name)) continue;
|
||||
const pkg = await manifest(name);
|
||||
if (!pkg) continue;
|
||||
seen.add(name);
|
||||
await resolveTree(Object.keys(pkg.dependencies ?? {}), seen);
|
||||
}
|
||||
return seen;
|
||||
}
|
||||
|
||||
async function entry(name) {
|
||||
const pkg = await manifest(name);
|
||||
const file = (await readdir(`node_modules/${name}`)).find((f) => LICENSE_FILE.test(f));
|
||||
if (!file) return null;
|
||||
const text = await readFile(`node_modules/${name}/${file}`, 'utf8');
|
||||
const who = typeof pkg.author === 'string' ? pkg.author : pkg.author?.name;
|
||||
return { name, license: pkg.license, who, text: text.trim() };
|
||||
}
|
||||
|
||||
const found = [];
|
||||
const missing = [];
|
||||
for (const name of [...(await resolveTree(roots))].sort()) {
|
||||
const found_ = await entry(name);
|
||||
(found_ ? found : missing).push(found_ ?? name);
|
||||
}
|
||||
|
||||
const header = `Third-party licenses bundled into EdenText
|
||||
==========================================
|
||||
|
||||
EdenText itself is licensed under the AGPL-3.0-only; see the LICENSE file in the
|
||||
source repository (linked from the app's About dialog). The components below are
|
||||
compiled into the application bundle and keep their own licenses, reproduced in
|
||||
full. Fonts and language data carry their licenses beside them, under
|
||||
/font-licenses/, /dictionaries/ and /thesaurus/.
|
||||
|
||||
Generated by scripts/collect-licenses.mjs — do not edit by hand.
|
||||
`;
|
||||
|
||||
const body = found
|
||||
.map(({ name, license, who, text }) => {
|
||||
const head = `${name}${license ? ` — ${license}` : ''}${who ? ` — ${who}` : ''}`;
|
||||
return `${'-'.repeat(78)}\n${head}\n${'-'.repeat(78)}\n\n${text}\n`;
|
||||
})
|
||||
.join('\n');
|
||||
|
||||
// Packages that ship no license file still declare one; name them rather than
|
||||
// let them vanish from the list.
|
||||
const declared = await Promise.all(
|
||||
missing.map(async (name) => ` ${name} — ${(await manifest(name))?.license ?? 'unknown'}`),
|
||||
);
|
||||
const tail = declared.length
|
||||
? `\n${'-'.repeat(78)}\nDeclared in package metadata, no license file shipped upstream\n${'-'.repeat(78)}\n\n${declared.join('\n')}\n`
|
||||
: '';
|
||||
|
||||
await writeFile('public/licenses.txt', `${header}\n${body}${tail}`);
|
||||
console.log(`public/licenses.txt: ${found.length} licenses, ${missing.length} declared-only`);
|
||||
Reference in new issue
Block a user