Skip to content

Commit 97c70b9

Browse files
authored
Merge pull request #300 from arkedge/feat/add-third-party-licenses
Add generation and bundling of third-party licenses
2 parents c1fc05e + 62e88f0 commit 97c70b9

5 files changed

Lines changed: 69 additions & 2 deletions

File tree

client-ui/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ node_modules
44
/.react-router
55
/build
66
.env
7+
/THIRD_PARTY_LICENSES.txt

client-ui/Dockerfile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
FROM node:24-bullseye-slim AS base
22

33
ENV NODE_ENV=production
4+
ENV CI=true
45

56
# https://github.com/nodejs/corepack/issues/612#issuecomment-2629496091
67
RUN npm install -g corepack@latest
@@ -18,6 +19,7 @@ RUN pnpm install --frozen-lockfile --prod=false
1819

1920
COPY . .
2021
RUN pnpm run build
22+
RUN pnpm run gen:licenses
2123
RUN pnpm prune --prod
2224

2325
# runtime stage
@@ -30,6 +32,7 @@ COPY package.json .
3032
COPY --from=build /app/node_modules /app/node_modules
3133
COPY --from=build /app/build/client /app/build/client
3234
COPY --from=build /app/build/server /app/build/server
35+
COPY --from=build /app/THIRD_PARTY_LICENSES.txt /app/THIRD_PARTY_LICENSES.txt
3336

3437
RUN corepack install
3538

client-ui/eslint.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export default defineConfig([
1515
globals: { ...globals.browser, ...globals.node },
1616
},
1717
},
18-
globalIgnores(["build/", ".react-router/"]),
18+
globalIgnores(["build/", ".react-router/", "scripts/"]),
1919
eslint.configs.recommended,
2020

2121
// TypeScript

client-ui/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"lint": "prettier --check . && eslint",
1111
"start": "react-router-serve ./build/server/index.js",
1212
"typecheck": "tsc",
13-
"gen:proto": "buf generate"
13+
"gen:proto": "buf generate",
14+
"gen:licenses": "node scripts/gen-third-party-licenses.mjs"
1415
},
1516
"dependencies": {
1617
"@blueprintjs/core": "^6.0.0",
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#!/usr/bin/env node
2+
// Generates THIRD_PARTY_LICENSES.txt covering every production dependency
3+
// bundled into the distributed container image, as required for binary
4+
// redistribution under most OSS licenses (MIT/BSD/Apache-2.0 etc.).
5+
import { execFileSync } from "node:child_process";
6+
import { readdirSync, readFileSync, writeFileSync } from "node:fs";
7+
import { join } from "node:path";
8+
9+
const LICENSE_FILE_RE = /^(license|licence|copying)(\.(md|txt))?$/i;
10+
11+
function findLicenseText(dir) {
12+
let files;
13+
try {
14+
files = readdirSync(dir);
15+
} catch {
16+
return null;
17+
}
18+
const licenseFile = files.find((f) => LICENSE_FILE_RE.test(f));
19+
if (!licenseFile) return null;
20+
return readFileSync(join(dir, licenseFile), "utf8").trim();
21+
}
22+
23+
const raw = execFileSync("pnpm", ["licenses", "list", "--json", "--prod"], {
24+
maxBuffer: 1024 * 1024 * 32,
25+
}).toString();
26+
const byLicense = JSON.parse(raw);
27+
28+
const packages = [];
29+
for (const [license, pkgs] of Object.entries(byLicense)) {
30+
for (const pkg of pkgs) {
31+
packages.push({ ...pkg, license });
32+
}
33+
}
34+
packages.sort((a, b) => a.name.localeCompare(b.name));
35+
36+
const sections = packages.map((pkg) => {
37+
const header = [
38+
`${pkg.name}@${pkg.versions.join(", ")}`,
39+
`License: ${pkg.license}`,
40+
pkg.homepage ? `Homepage: ${pkg.homepage}` : null,
41+
]
42+
.filter(Boolean)
43+
.join("\n");
44+
45+
const text = findLicenseText(pkg.paths[0]);
46+
const body =
47+
text ?? "(license text not found in package; see homepage above)";
48+
49+
return `${header}\n\n${body}`;
50+
});
51+
52+
const banner =
53+
"This file lists the third-party open source dependencies bundled into\n" +
54+
"the clover-ui container image, along with their license texts, as\n" +
55+
"required for redistribution.\n";
56+
57+
const out = `${banner}\n${"=".repeat(80)}\n\n${sections.join(`\n\n${"=".repeat(80)}\n\n`)}\n`;
58+
59+
writeFileSync("THIRD_PARTY_LICENSES.txt", out);
60+
console.log(
61+
`Wrote THIRD_PARTY_LICENSES.txt covering ${packages.length} packages.`,
62+
);

0 commit comments

Comments
 (0)