Files
Jungfraujoch/frontend/scripts/generate-third-party-licenses.mjs
T
leonarski_f 75e401f0e5
Build Packages / Unit tests (push) Successful in 1h31m59s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 8m43s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 10m5s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 9m27s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 8m56s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 9m24s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 10m27s
Build Packages / build:rpm (rocky8) (push) Successful in 9m20s
Build Packages / build:rpm (rocky9) (push) Successful in 10m50s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 9m54s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 8m38s
Build Packages / DIALS test (push) Successful in 12m13s
Build Packages / XDS test (durin plugin) (push) Successful in 7m8s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 7m8s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m50s
Build Packages / Generate python client (push) Successful in 16s
Build Packages / Build documentation (push) Successful in 50s
Build Packages / Create release (push) Skipped
v1.0.0-rc.153 (#63)
This is an UNSTABLE release. It includes many experimental features, as well as many AI generated fixes. We recommend using rc.152 for production use.

* jfjoch_broker: Add EXPERIMENTAL pixelrefine mode for image processing
* jfjoch_broker: Allow to load user mask from 8-bit and 16-bit TIFF files
* jfjoch_broker: Add ROI calculation in non-FPGA workflow
* jfjoch_broker: Fixes to TCP image pusher
* jfjoch_broker: Remove NUMA bindings
* jfjoch_broker: Improvements to indexing
* jfjoch_broker: For PSI EIGER, trimming energies are taken from the detector configuration (now compulsory) instead of hardcoded values
* jfjoch_writer: Save ROI definitions and the per-pixel ROI bitmap in the master file; azimuthal ROIs support phi (angular) sectors
* jfjoch_viewer: Major redesign with dockable panels and saved layouts, plus on-canvas creation/move/resize of box, circle and azimuthal ROIs
* jfjoch_viewer: Run jfjoch_process reprocessing jobs from inside the GUI and overlay per-run results

Reviewed-on: #63
2026-06-23 20:29:49 +02:00

122 lines
4.4 KiB
JavaScript

// Generate frontend/dist/THIRD_PARTY_LICENSES.txt: the third-party attribution file for the
// npm packages bundled into the built frontend.
//
// Self-contained (no extra dependency): it asks npm for the *production* dependency tree, then
// reads each package's own license file from node_modules and concatenates them. Run via
// `npm run licenses` (also invoked by the CMake `frontend` build target).
import { execFileSync } from "node:child_process";
import { readFileSync, readdirSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
const frontendDir = join(dirname(fileURLToPath(import.meta.url)), "..");
const nodeModules = join(frontendDir, "node_modules");
const outFile = join(frontendDir, "dist", "THIRD_PARTY_LICENSES.txt");
const LICENSE_FILE = /^(licen[sc]e|copying|notice)([.-].*)?$/i;
// Set of "name@version" strings that actually ship in production (excludes devDependencies).
function productionPackages() {
const json = execFileSync("npm", ["ls", "--omit=dev", "--all", "--json"], {
cwd: frontendDir,
encoding: "utf8",
maxBuffer: 64 * 1024 * 1024,
});
const set = new Set();
const walk = (deps) => {
for (const [name, info] of Object.entries(deps || {})) {
if (info.version) set.add(`${name}@${info.version}`);
walk(info.dependencies);
}
};
walk(JSON.parse(json).dependencies);
return set;
}
// Map "name@version" -> package directory, by scanning every node_modules in the install tree.
function installedPackages() {
const map = new Map();
const scan = (nm) => {
if (!existsSync(nm)) return;
for (const entry of readdirSync(nm)) {
if (entry.startsWith(".")) continue;
if (entry.startsWith("@")) {
for (const sub of readdirSync(join(nm, entry))) record(join(nm, entry, sub));
} else {
record(join(nm, entry));
}
}
};
const record = (dir) => {
const pkgJson = join(dir, "package.json");
if (!existsSync(pkgJson)) return;
try {
const pkg = JSON.parse(readFileSync(pkgJson, "utf8"));
if (pkg.name && pkg.version) map.set(`${pkg.name}@${pkg.version}`, { dir, pkg });
} catch { /* ignore unparseable package.json */ }
scan(join(dir, "node_modules"));
};
scan(nodeModules);
return map;
}
function findLicenseText(dir) {
const file = readdirSync(dir).find((f) => LICENSE_FILE.test(f));
if (!file) return null;
try {
return readFileSync(join(dir, file), "utf8").trimEnd();
} catch {
return null;
}
}
function licenseId(pkg) {
if (typeof pkg.license === "string") return pkg.license;
if (pkg.license?.type) return pkg.license.type;
if (Array.isArray(pkg.licenses)) return pkg.licenses.map((l) => l.type || l).join(" OR ");
return "UNKNOWN";
}
function repoUrl(pkg) {
const r = pkg.repository;
if (typeof r === "string") return r;
if (r?.url) return r.url.replace(/^git\+/, "").replace(/\.git$/, "");
return pkg.homepage || "";
}
const prod = productionPackages();
const installed = installedPackages();
const blocks = [];
const missing = [];
for (const id of [...prod].sort((a, b) => a.localeCompare(b))) {
const found = installed.get(id);
if (!found) continue; // present in tree report but not on disk (e.g. bundled) — skip
const { dir, pkg } = found;
const text = findLicenseText(dir);
if (!text) missing.push(`${id} (${licenseId(pkg)})`);
const url = repoUrl(pkg);
blocks.push(
`${"=".repeat(80)}\n` +
`${id}${licenseId(pkg)}\n` +
(url ? `${url}\n` : "") +
`${"=".repeat(80)}\n\n` +
(text || `License: ${licenseId(pkg)} (no license file shipped in this package)`),
);
}
const header =
"Third-party software bundled in the Jungfraujoch frontend\n" +
"=========================================================\n\n" +
"The Jungfraujoch frontend is licensed under GPL-3.0. It bundles the following npm packages\n" +
"(production dependencies). Each package's license and notice text follows.\n\n" +
`Generated by scripts/generate-third-party-licenses.mjs — ${prod.size} packages.\n` +
(missing.length
? `\nPackages without a bundled license file (license field shown above):\n ${missing.join("\n ")}\n`
: "");
mkdirSync(dirname(outFile), { recursive: true });
writeFileSync(outFile, header + "\n" + blocks.join("\n\n") + "\n", "utf8");
console.log(`Wrote ${outFile} (${blocks.length} packages, ${missing.length} without license file).`);