mirror of
https://git.quad4.io/RNS-Things/MeshChatX.git
synced 2026-10-23 17:13:25 +00:00
feat(licenses): add functionality to collect and write frontend licenses and third-party notices; implement embedded data paths and update build process to include license artifacts
This commit is contained in:
4 files changed
+238
-13
No files matched your search
+12
@@ -18,6 +18,18 @@ changelog_path = ROOT / "CHANGELOG.md"
|
||||
if changelog_path.exists():
|
||||
include_files.append((str(changelog_path), "CHANGELOG.md"))
|
||||
|
||||
frontend_licenses_path = (
|
||||
ROOT / "meshchatx" / "src" / "backend" / "data" / "licenses_frontend.json"
|
||||
)
|
||||
if frontend_licenses_path.exists():
|
||||
include_files.append((str(frontend_licenses_path), "licenses_frontend.json"))
|
||||
|
||||
third_party_notices_path = (
|
||||
ROOT / "meshchatx" / "src" / "backend" / "data" / "THIRD_PARTY_NOTICES.txt"
|
||||
)
|
||||
if third_party_notices_path.exists():
|
||||
include_files.append((str(third_party_notices_path), "THIRD_PARTY_NOTICES.txt"))
|
||||
|
||||
if PUBLIC_DIR.exists() and PUBLIC_DIR.is_dir():
|
||||
include_files.append((str(PUBLIC_DIR), "public"))
|
||||
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
Reticulum MeshChatX - Third-party notices
|
||||
Generated at: 2026-04-15T07:29:13.270941Z
|
||||
Frontend source: embedded
|
||||
|
||||
Python dependencies
|
||||
-------------------
|
||||
aiohappyeyeballs 2.6.1
|
||||
License: PSF-2.0
|
||||
Author: J. Nick Koston
|
||||
aiohttp 3.13.5
|
||||
License: Apache-2.0 AND MIT
|
||||
Author: —
|
||||
aiohttp-session 2.12.1
|
||||
License: Apache 2
|
||||
Author: Andrew Svetlov
|
||||
aiosignal 1.4.0
|
||||
License: Apache 2.0
|
||||
Author: aiohttp team <team@aiohttp.org>
|
||||
attrs 26.1.0
|
||||
License: MIT
|
||||
Author: Hynek Schlawack <hs@ox.cx>
|
||||
audioop-lts 0.2.2
|
||||
License: PSF-2.0
|
||||
Author: —
|
||||
bcrypt 5.0.0
|
||||
License: Apache-2.0
|
||||
Author: The Python Cryptographic Authority developers <cryptography-dev@python.org>
|
||||
cffi 2.0.0
|
||||
License: MIT
|
||||
Author: Armin Rigo, Maciej Fijalkowski
|
||||
cryptography 46.0.7
|
||||
License: Apache-2.0 OR BSD-3-Clause
|
||||
Author: The Python Cryptographic Authority and individual contributors <cryptography-dev@python.org>
|
||||
frozenlist 1.8.0
|
||||
License: Apache-2.0
|
||||
Author: aiohttp team <team@aiohttp.org>
|
||||
idna 3.11
|
||||
License: BSD-3-Clause
|
||||
Author: Kim Davies <kim+pypi@gumleaf.org>
|
||||
jaraco.context 6.1.2
|
||||
License: MIT
|
||||
Author: "Jason R. Coombs" <jaraco@jaraco.com>
|
||||
lxmf 0.9.4
|
||||
License: Reticulum License
|
||||
Author: Mark Qvist
|
||||
lxmfy 1.6.1
|
||||
License: BSD-0-Clause
|
||||
Author: Quad4
|
||||
lxst 0.4.6
|
||||
License: Other/Proprietary License
|
||||
Author: Mark Qvist
|
||||
multidict 6.7.1
|
||||
License: Apache License 2.0
|
||||
Author: Andrew Svetlov
|
||||
numpy 2.4.4
|
||||
License: BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0
|
||||
Author: Travis E. Oliphant et al.
|
||||
ply 3.11
|
||||
License: BSD
|
||||
Author: David Beazley
|
||||
propcache 0.4.1
|
||||
License: Apache-2.0
|
||||
Author: Andrew Svetlov
|
||||
psutil 7.2.2
|
||||
License: BSD-3-Clause
|
||||
Author: Giampaolo Rodola
|
||||
pycodec2 4.1.1
|
||||
License: OSI Approved :: BSD License
|
||||
Author: Grzegorz Milka
|
||||
pycparser 3.0
|
||||
License: BSD-3-Clause
|
||||
Author: Eli Bendersky <eliben@gmail.com>
|
||||
pyserial 3.5
|
||||
License: BSD
|
||||
Author: Chris Liechti
|
||||
reticulum-meshchatx 4.4.0
|
||||
License: MIT
|
||||
Author: Sudo-Ivan
|
||||
rns 1.1.5
|
||||
License: Reticulum License
|
||||
Author: Mark Qvist
|
||||
websockets 16.0
|
||||
License: BSD-3-Clause
|
||||
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
|
||||
yarl 1.23.0
|
||||
License: Apache-2.0
|
||||
Author: Andrew Svetlov
|
||||
|
||||
Node dependencies
|
||||
-----------------
|
||||
No entries.
|
||||
@@ -6,6 +6,7 @@ import importlib.metadata
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tomllib
|
||||
from datetime import UTC, datetime
|
||||
from pathlib import Path
|
||||
@@ -15,6 +16,9 @@ from packaging.requirements import Requirement
|
||||
from packaging.utils import canonicalize_name
|
||||
|
||||
_ROOT_DIST_CANDIDATES = ("reticulum-meshchatx", "reticulum_meshchatx")
|
||||
_DATA_SUBPATH = Path("meshchatx") / "src" / "backend" / "data"
|
||||
_FRONTEND_LICENSES_FILENAME = "licenses_frontend.json"
|
||||
_THIRD_PARTY_NOTICES_FILENAME = "THIRD_PARTY_NOTICES.txt"
|
||||
|
||||
|
||||
def _repo_root() -> Path:
|
||||
@@ -162,18 +166,35 @@ def _flatten_pnpm_licenses_json(data: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
return out
|
||||
|
||||
|
||||
def _embedded_data_paths(filename: str) -> list[Path]:
|
||||
paths = [Path(__file__).resolve().parent / "data" / filename]
|
||||
exe_parent = Path(sys.executable).resolve().parent
|
||||
paths.append(exe_parent / filename)
|
||||
paths.append(exe_parent / "data" / filename)
|
||||
paths.append(exe_parent / _DATA_SUBPATH / filename)
|
||||
seen: set[str] = set()
|
||||
unique_paths: list[Path] = []
|
||||
for path in paths:
|
||||
key = str(path)
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
unique_paths.append(path)
|
||||
return unique_paths
|
||||
|
||||
|
||||
def _load_embedded_frontend_licenses() -> list[dict[str, Any]] | None:
|
||||
data_dir = Path(__file__).resolve().parent / "data"
|
||||
path = data_dir / "licenses_frontend.json"
|
||||
if not path.is_file():
|
||||
return None
|
||||
try:
|
||||
raw = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return None
|
||||
if not isinstance(raw, list):
|
||||
return None
|
||||
return [x for x in raw if isinstance(x, dict)]
|
||||
for path in _embedded_data_paths(_FRONTEND_LICENSES_FILENAME):
|
||||
if not path.is_file():
|
||||
continue
|
||||
try:
|
||||
raw = json.loads(path.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
continue
|
||||
if not isinstance(raw, list):
|
||||
continue
|
||||
return [x for x in raw if isinstance(x, dict)]
|
||||
return None
|
||||
|
||||
|
||||
def collect_frontend_licenses() -> tuple[list[dict[str, Any]], str]:
|
||||
@@ -221,3 +242,82 @@ def build_licenses_payload() -> dict[str, Any]:
|
||||
"frontend_source": fe_source,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def render_third_party_notices(payload: dict[str, Any]) -> str:
|
||||
"""Render third-party dependency notices as plain text."""
|
||||
meta = payload.get("meta", {}) if isinstance(payload, dict) else {}
|
||||
generated_at = str(meta.get("generated_at", "unknown"))
|
||||
frontend_source = str(meta.get("frontend_source", "unknown"))
|
||||
lines = [
|
||||
"Reticulum MeshChatX - Third-party notices",
|
||||
f"Generated at: {generated_at}",
|
||||
f"Frontend source: {frontend_source}",
|
||||
"",
|
||||
]
|
||||
sections: list[tuple[str, list[dict[str, Any]]]] = [
|
||||
("Python dependencies", payload.get("backend", [])),
|
||||
("Node dependencies", payload.get("frontend", [])),
|
||||
]
|
||||
for title, rows in sections:
|
||||
lines.append(title)
|
||||
lines.append("-" * len(title))
|
||||
if not isinstance(rows, list) or not rows:
|
||||
lines.append("No entries.")
|
||||
lines.append("")
|
||||
continue
|
||||
for row in rows:
|
||||
if not isinstance(row, dict):
|
||||
continue
|
||||
name = str(row.get("name", "?"))
|
||||
version = str(row.get("version", "?"))
|
||||
author = str(row.get("author", "—"))
|
||||
license_name = str(row.get("license", "—"))
|
||||
lines.append(f"{name} {version}")
|
||||
lines.append(f" License: {license_name}")
|
||||
lines.append(f" Author: {author}")
|
||||
lines.append("")
|
||||
return "\n".join(lines).rstrip() + "\n"
|
||||
|
||||
|
||||
def write_embedded_license_artifacts(repo_root: Path | None = None) -> dict[str, Any]:
|
||||
"""Generate and write embedded license metadata and notices artifacts."""
|
||||
if repo_root is None:
|
||||
repo_root = _repo_root()
|
||||
data_dir = repo_root / _DATA_SUBPATH
|
||||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
payload = build_licenses_payload()
|
||||
frontend = payload.get("frontend", [])
|
||||
meta = payload.get("meta", {}) if isinstance(payload, dict) else {}
|
||||
frontend_source = str(meta.get("frontend_source", "unknown"))
|
||||
frontend_path = data_dir / _FRONTEND_LICENSES_FILENAME
|
||||
notices_path = data_dir / _THIRD_PARTY_NOTICES_FILENAME
|
||||
frontend_rows = frontend if isinstance(frontend, list) else []
|
||||
should_write_frontend = (
|
||||
bool(frontend_rows) or not frontend_path.exists() or frontend_source == "pnpm"
|
||||
)
|
||||
if should_write_frontend:
|
||||
frontend_path.write_text(
|
||||
json.dumps(frontend_rows, indent=2, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
notices_path.write_text(render_third_party_notices(payload), encoding="utf-8")
|
||||
return {
|
||||
"frontend_path": str(frontend_path),
|
||||
"frontend_count": len(frontend_rows),
|
||||
"frontend_written": should_write_frontend,
|
||||
"notices_path": str(notices_path),
|
||||
}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "--write-artifacts":
|
||||
result = write_embedded_license_artifacts()
|
||||
print(json.dumps(result, indent=2))
|
||||
return 0
|
||||
print(json.dumps(build_licenses_payload(), indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -91,11 +91,33 @@ try {
|
||||
|
||||
const cmdParts = pythonCmd.trim().split(/\s+/).filter(Boolean);
|
||||
const cmd = cmdParts[0];
|
||||
const args = [...cmdParts.slice(1), "cx_setup.py", "build"];
|
||||
const baseArgs = cmdParts.slice(1);
|
||||
const licensesArgs = [...baseArgs, "-m", "meshchatx.src.backend.licenses_collector", "--write-artifacts"];
|
||||
const args = [...baseArgs, "cx_setup.py", "build"];
|
||||
|
||||
let spawnCmd = cmd;
|
||||
let spawnArgs = args;
|
||||
let spawnArgs = licensesArgs;
|
||||
const rosettaX64 = isDarwin && arch === "x64" && process.arch === "arm64" && !process.env.PYTHON_CMD;
|
||||
if (rosettaX64) {
|
||||
spawnCmd = "arch";
|
||||
spawnArgs = ["-x86_64", cmd, ...licensesArgs];
|
||||
}
|
||||
|
||||
console.log("Generating embedded third-party license artifacts...");
|
||||
const licensesResult = spawnSync(spawnCmd, spawnArgs, {
|
||||
stdio: "inherit",
|
||||
shell: false,
|
||||
env: env,
|
||||
});
|
||||
if (licensesResult.error) {
|
||||
throw licensesResult.error;
|
||||
}
|
||||
if (licensesResult.status !== 0) {
|
||||
process.exit(licensesResult.status || 1);
|
||||
}
|
||||
|
||||
spawnCmd = cmd;
|
||||
spawnArgs = args;
|
||||
if (rosettaX64) {
|
||||
spawnCmd = "arch";
|
||||
spawnArgs = ["-x86_64", cmd, ...args];
|
||||
|
||||
Reference in new issue
Block a user