mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-03-28 17:43:05 +01:00
Add licenses explicitly (probably should have been doing this for a while; oops! Apologies!)
This commit is contained in:
@@ -29,6 +29,7 @@ frontend/src/test/
|
||||
# Docs
|
||||
*.md
|
||||
!README.md
|
||||
!LICENSES.md
|
||||
|
||||
# Other
|
||||
references/
|
||||
|
||||
@@ -29,6 +29,9 @@ RUN uv sync --frozen --no-dev
|
||||
# Copy application code
|
||||
COPY app/ ./app/
|
||||
|
||||
# Copy license attributions
|
||||
COPY LICENSES.md ./
|
||||
|
||||
# Copy built frontend from first stage
|
||||
COPY --from=frontend-builder /build/dist ./frontend/dist
|
||||
|
||||
|
||||
1475
LICENSES.md
Normal file
1475
LICENSES.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -69,6 +69,7 @@ reportAttributeAccessIssue = "warning"
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"httpx>=0.28.1",
|
||||
"pip-licenses>=5.0.0",
|
||||
"pytest>=9.0.2",
|
||||
"pytest-asyncio>=1.3.0",
|
||||
"pytest-xdist>=3.0",
|
||||
|
||||
@@ -35,9 +35,24 @@ PID_BACKEND_LINT=$!
|
||||
) &
|
||||
PID_FRONTEND_LINT=$!
|
||||
|
||||
(
|
||||
echo -e "${BLUE}[licenses]${NC} Checking LICENSES.md freshness..."
|
||||
cd "$SCRIPT_DIR"
|
||||
TMPLIC=$(mktemp)
|
||||
trap "rm -f \$TMPLIC" EXIT
|
||||
bash scripts/collect_licenses.sh "$TMPLIC"
|
||||
if ! diff -q "$TMPLIC" LICENSES.md > /dev/null 2>&1; then
|
||||
echo -e "${RED}[licenses]${NC} LICENSES.md is stale — run scripts/collect_licenses.sh"
|
||||
exit 1
|
||||
fi
|
||||
echo -e "${GREEN}[licenses]${NC} Passed!"
|
||||
) &
|
||||
PID_LICENSES=$!
|
||||
|
||||
FAIL=0
|
||||
wait $PID_BACKEND_LINT || FAIL=1
|
||||
wait $PID_FRONTEND_LINT || FAIL=1
|
||||
wait $PID_LICENSES || FAIL=1
|
||||
if [ $FAIL -ne 0 ]; then
|
||||
echo -e "${RED}Phase 1 failed — aborting.${NC}"
|
||||
exit 1
|
||||
|
||||
124
scripts/collect_licenses.sh
Executable file
124
scripts/collect_licenses.sh
Executable file
@@ -0,0 +1,124 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Collect third-party license texts into LICENSES.md
|
||||
# Usage: scripts/collect_licenses.sh [output-path]
|
||||
# output-path defaults to LICENSES.md at the repo root
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
OUT="${1:-$REPO_ROOT/LICENSES.md}"
|
||||
|
||||
# ── Backend (Python) — uses pip-licenses ─────────────────────────────
|
||||
backend_licenses() {
|
||||
cd "$REPO_ROOT"
|
||||
|
||||
# Extract direct dependency names from pyproject.toml
|
||||
local packages
|
||||
packages=$(uv run python3 -c "
|
||||
import re, tomllib
|
||||
with open('pyproject.toml', 'rb') as f:
|
||||
deps = tomllib.load(f)['project']['dependencies']
|
||||
names = [re.split(r'[\[><=!;]', d)[0].strip() for d in deps]
|
||||
print(' '.join(names))
|
||||
")
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
uv run pip-licenses \
|
||||
--packages $packages \
|
||||
--with-license-file \
|
||||
--no-license-path \
|
||||
--format=json \
|
||||
| uv run python3 -c "
|
||||
import json, sys
|
||||
|
||||
data = sorted(json.load(sys.stdin), key=lambda d: d['Name'].lower())
|
||||
for d in data:
|
||||
name = d['Name']
|
||||
version = d['Version']
|
||||
lic = d.get('License', 'Unknown')
|
||||
text = d.get('LicenseText', '').strip()
|
||||
|
||||
print(f'### {name} ({version}) — {lic}\n')
|
||||
if text and text != 'UNKNOWN':
|
||||
print('<details>')
|
||||
print('<summary>Full license text</summary>')
|
||||
print()
|
||||
print('\`\`\`')
|
||||
print(text)
|
||||
print('\`\`\`')
|
||||
print()
|
||||
print('</details>')
|
||||
else:
|
||||
print('*License file not found in package metadata.*')
|
||||
print()
|
||||
"
|
||||
}
|
||||
|
||||
# ── Frontend (npm) ───────────────────────────────────────────────────
|
||||
frontend_licenses() {
|
||||
cd "$REPO_ROOT/frontend"
|
||||
|
||||
node -e "
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
||||
const depNames = Object.keys(pkg.dependencies || {}).sort((a, b) =>
|
||||
a.toLowerCase().localeCompare(b.toLowerCase())
|
||||
);
|
||||
|
||||
for (const name of depNames) {
|
||||
const pkgDir = path.join('node_modules', name);
|
||||
let version = 'unknown';
|
||||
let licenseType = 'Unknown';
|
||||
let licenseText = null;
|
||||
|
||||
// Read package.json for version + license type
|
||||
try {
|
||||
const depPkg = JSON.parse(fs.readFileSync(path.join(pkgDir, 'package.json'), 'utf8'));
|
||||
version = depPkg.version || version;
|
||||
licenseType = depPkg.license || licenseType;
|
||||
} catch {}
|
||||
|
||||
// Find license file (case-insensitive search)
|
||||
try {
|
||||
const files = fs.readdirSync(pkgDir);
|
||||
const licFile = files.find(f => /^(licen[sc]e|copying)/i.test(f));
|
||||
if (licFile) {
|
||||
licenseText = fs.readFileSync(path.join(pkgDir, licFile), 'utf8').trim();
|
||||
}
|
||||
} catch {}
|
||||
|
||||
console.log('### ' + name + ' (' + version + ') — ' + licenseType + '\n');
|
||||
if (licenseText) {
|
||||
console.log('<details>');
|
||||
console.log('<summary>Full license text</summary>');
|
||||
console.log();
|
||||
console.log('\`\`\`');
|
||||
console.log(licenseText);
|
||||
console.log('\`\`\`');
|
||||
console.log();
|
||||
console.log('</details>');
|
||||
} else {
|
||||
console.log('*License file not found in package.*');
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
"
|
||||
}
|
||||
|
||||
# ── Assemble ─────────────────────────────────────────────────────────
|
||||
{
|
||||
echo "# Third-Party Licenses"
|
||||
echo
|
||||
echo "Auto-generated by \`scripts/collect_licenses.sh\` — do not edit by hand."
|
||||
echo
|
||||
echo "## Backend (Python) Dependencies"
|
||||
echo
|
||||
backend_licenses
|
||||
echo "## Frontend (npm) Dependencies"
|
||||
echo
|
||||
frontend_licenses
|
||||
} > "$OUT"
|
||||
|
||||
echo "Wrote $OUT" >&2
|
||||
36
uv.lock
generated
36
uv.lock
generated
@@ -420,6 +420,19 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c4/cb/00451c3cf31790287768bb12c6bec834f5d292eaf3022afc88e14b8afc94/paho_mqtt-2.1.0-py3-none-any.whl", hash = "sha256:6db9ba9b34ed5bc6b6e3812718c7e06e2fd7444540df2455d2c51bd58808feee", size = 67219 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pip-licenses"
|
||||
version = "5.5.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "prettytable" },
|
||||
{ name = "tomli", marker = "python_full_version < '3.11'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/44/4c/b4be9024dae3b5b3c0a6c58cc1d4a35fffe51c3adb835350cb7dcd43b5cd/pip_licenses-5.5.1.tar.gz", hash = "sha256:7df370e6e5024a3f7449abf8e4321ef868ba9a795698ad24ab6851f3e7fc65a7", size = 49108 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a0/a3/0b369cdffef3746157712804f1ded9856c75aa060217ee206f742c74e753/pip_licenses-5.5.1-py3-none-any.whl", hash = "sha256:ed5e229a93760e529cfa7edaec6630b5a2cd3874c1bddb8019e5f18a723fdead", size = 22108 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pluggy"
|
||||
version = "1.6.0"
|
||||
@@ -429,6 +442,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "prettytable"
|
||||
version = "3.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "wcwidth" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/79/45/b0847d88d6cfeb4413566738c8bbf1e1995fad3d42515327ff32cc1eb578/prettytable-3.17.0.tar.gz", hash = "sha256:59f2590776527f3c9e8cf9fe7b66dd215837cca96a9c39567414cbc632e8ddb0", size = 67892 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/8c/83087ebc47ab0396ce092363001fa37c17153119ee282700c0713a195853/prettytable-3.17.0-py3-none-any.whl", hash = "sha256:aad69b294ddbe3e1f95ef8886a060ed1666a0b83018bbf56295f6f226c43d287", size = 34433 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pycayennelpp"
|
||||
version = "2.4.0"
|
||||
@@ -922,6 +947,7 @@ test = [
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "httpx" },
|
||||
{ name = "pip-licenses" },
|
||||
{ name = "pyright" },
|
||||
{ name = "pytest" },
|
||||
{ name = "pytest-asyncio" },
|
||||
@@ -949,6 +975,7 @@ provides-extras = ["test"]
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
{ name = "httpx", specifier = ">=0.28.1" },
|
||||
{ name = "pip-licenses", specifier = ">=5.0.0" },
|
||||
{ name = "pyright", specifier = ">=1.1.390" },
|
||||
{ name = "pytest", specifier = ">=9.0.2" },
|
||||
{ name = "pytest-asyncio", specifier = ">=1.3.0" },
|
||||
@@ -1237,6 +1264,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/35/a2/8e3becb46433538a38726c948d3399905a4c7cabd0df578ede5dc51f0ec2/wcwidth-0.6.0.tar.gz", hash = "sha256:cdc4e4262d6ef9a1a57e018384cbeb1208d8abbc64176027e2c2455c81313159", size = 159684 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "websockets"
|
||||
version = "15.0.1"
|
||||
|
||||
Reference in New Issue
Block a user