From c336127006333b9ffef20eca159d8b2eb758c4a4 Mon Sep 17 00:00:00 2001 From: Steffen Becker Date: Thu, 20 Aug 2026 15:15:52 +0200 Subject: [PATCH] fix: ship every bundled MIT notice and link licenses.txt in About collect-licenses falls back to the README's License section (only when it carries a copyright line) for packages without a LICENSE file; hash.js and isarray now ship their full notice. The About dialog links /licenses.txt, which was previously reachable only by knowing the URL. --- public/licenses.txt | 57 +++++++++++++++++++++++++-- scripts/collect-licenses.mjs | 25 ++++++++++-- src/lib/components/AboutDialog.svelte | 10 +++++ src/lib/i18n/locales/de.ts | 1 + src/lib/i18n/locales/en.ts | 1 + 5 files changed, 87 insertions(+), 7 deletions(-) diff --git a/public/licenses.txt b/public/licenses.txt index c138fe0..fbd9acc 100644 --- a/public/licenses.txt +++ b/public/licenses.txt @@ -1581,6 +1581,33 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +hash.js — MIT — Fedor Indutny +------------------------------------------------------------------------------ + +This software is licensed under the MIT License. + +Copyright Fedor Indutny, 2014. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + ------------------------------------------------------------------------------ html2canvas — MIT — Niklas von Hertzen ------------------------------------------------------------------------------ @@ -1705,6 +1732,32 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +isarray — MIT — Julian Gruber +------------------------------------------------------------------------------ + +(MIT) + +Copyright (c) 2013 Julian Gruber + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + ------------------------------------------------------------------------------ jspdf — MIT ------------------------------------------------------------------------------ @@ -3635,11 +3688,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------ -Declared in package metadata, no license file shipped upstream +Declared in package metadata, no license text shipped upstream ------------------------------------------------------------------------------ emscripten-wasm-loader — MIT - hash.js — MIT is-reference — MIT - isarray — MIT locate-character — MIT diff --git a/scripts/collect-licenses.mjs b/scripts/collect-licenses.mjs index 2132227..6bb09e1 100644 --- a/scripts/collect-licenses.mjs +++ b/scripts/collect-licenses.mjs @@ -32,13 +32,30 @@ async function resolveTree(names, seen = new Set()) { return seen; } +// Some packages carry their notice only in the README's License section; accept +// it as a fallback, but only when it contains an actual copyright line. +async function readmeLicense(name) { + const md = await readFile(`node_modules/${name}/README.md`, 'utf8').catch(() => ''); + const start = md.search(/^#{1,6} *licen[cs]e\b/im); + if (start < 0) return null; + const body = md.slice(md.indexOf('\n', start) + 1); + const next = body.search(/^#{1,6} /m); + const text = (next < 0 ? body : body.slice(0, next)) + .trim() + .replace(/</g, '<') + .replace(/>/g, '>'); + return /copyright/i.test(text) ? text : null; +} + 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 text = file + ? (await readFile(`node_modules/${name}/${file}`, 'utf8')).trim() + : await readmeLicense(name); + if (!text) return null; const who = typeof pkg.author === 'string' ? pkg.author : pkg.author?.name; - return { name, license: pkg.license, who, text: text.trim() }; + return { name, license: pkg.license, who, text }; } const found = []; @@ -73,7 +90,7 @@ 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` + ? `\n${'-'.repeat(78)}\nDeclared in package metadata, no license text shipped upstream\n${'-'.repeat(78)}\n\n${declared.join('\n')}\n` : ''; await writeFile('public/licenses.txt', `${header}\n${body}${tail}`); diff --git a/src/lib/components/AboutDialog.svelte b/src/lib/components/AboutDialog.svelte index 45f1efd..4de2f98 100644 --- a/src/lib/components/AboutDialog.svelte +++ b/src/lib/components/AboutDialog.svelte @@ -60,6 +60,8 @@ {t().about.viewOnGitHub} + + {t().about.licenses} @@ -192,4 +194,12 @@ border-color: var(--color-primary); color: #fff; } + + .licenses { + font-size: 0.75rem; + color: var(--color-text-muted); + } + .licenses:hover { + color: var(--color-text); + } diff --git a/src/lib/i18n/locales/de.ts b/src/lib/i18n/locales/de.ts index ad3e30a..e6bedc7 100644 --- a/src/lib/i18n/locales/de.ts +++ b/src/lib/i18n/locales/de.ts @@ -518,6 +518,7 @@ const de: Messages = { points: ['Free · Open Source · Private by Design'], version: (v) => `Version ${v}`, viewOnGitHub: 'Auf GitHub ansehen', + licenses: 'Lizenzen Dritter', }, findReplace: { dialogLabel: 'Suchen und ersetzen', diff --git a/src/lib/i18n/locales/en.ts b/src/lib/i18n/locales/en.ts index 9353890..dacde6d 100644 --- a/src/lib/i18n/locales/en.ts +++ b/src/lib/i18n/locales/en.ts @@ -524,6 +524,7 @@ const en = { points: ['Free · Open Source · Private by Design'], version: (v: string) => `Version ${v}`, viewOnGitHub: 'View on GitHub', + licenses: 'Third-party licenses', }, findReplace: { dialogLabel: 'Find and replace',