mirror of
https://github.com/Quad4-Software/MeshChatX.git
synced 2026-08-18 09:49:09 -04:00
feat(licenses): add backend license collection and JSON file, update license handling in the collector, and update build scripts to include backend licenses
This commit is contained in:
parent
5bc10bfb89
commit
db806684bc
8 changed files with 4205 additions and 3949 deletions
|
|
@ -30,6 +30,12 @@ frontend_licenses_path = (
|
|||
if frontend_licenses_path.exists():
|
||||
include_files.append((str(frontend_licenses_path), "licenses_frontend.json"))
|
||||
|
||||
backend_licenses_path = (
|
||||
ROOT / "meshchatx" / "src" / "backend" / "data" / "licenses_backend.json"
|
||||
)
|
||||
if backend_licenses_path.exists():
|
||||
include_files.append((str(backend_licenses_path), "licenses_backend.json"))
|
||||
|
||||
third_party_notices_path = (
|
||||
ROOT / "meshchatx" / "src" / "backend" / "data" / "THIRD_PARTY_NOTICES.txt"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Reticulum MeshChatX - Third-party notices
|
||||
Generated at: 2026-07-04T22:04:30.591013Z
|
||||
Generated at: 2026-07-04T22:50:15.826526Z
|
||||
Frontend source: pnpm
|
||||
|
||||
Python dependencies
|
||||
|
|
@ -19,6 +19,9 @@ aiosignal 1.4.0
|
|||
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>
|
||||
|
|
@ -79,9 +82,6 @@ reticulum-meshchatx 4.7.2
|
|||
rns 1.3.7
|
||||
License: Reticulum License
|
||||
Author: Mark Qvist
|
||||
typing_extensions 4.15.0
|
||||
License: PSF-2.0
|
||||
Author: "Guido van Rossum, Jukka Lehtosalo, Łukasz Langa, Michael Lee" <levkivskyi@gmail.com>
|
||||
websockets 16.0
|
||||
License: BSD-3-Clause
|
||||
Author: Aymeric Augustin <aymeric.augustin@m4x.org>
|
||||
|
|
|
|||
170
meshchatx/src/backend/data/licenses_backend.json
Normal file
170
meshchatx/src/backend/data/licenses_backend.json
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
[
|
||||
{
|
||||
"name": "aiohappyeyeballs",
|
||||
"version": "2.6.1",
|
||||
"author": "J. Nick Koston",
|
||||
"license": "PSF-2.0"
|
||||
},
|
||||
{
|
||||
"name": "aiohttp",
|
||||
"version": "3.14.1",
|
||||
"author": "—",
|
||||
"license": "Apache-2.0 AND MIT"
|
||||
},
|
||||
{
|
||||
"name": "aiohttp-session",
|
||||
"version": "2.12.1",
|
||||
"author": "Andrew Svetlov",
|
||||
"license": "Apache 2"
|
||||
},
|
||||
{
|
||||
"name": "aiosignal",
|
||||
"version": "1.4.0",
|
||||
"author": "aiohttp team <team@aiohttp.org>",
|
||||
"license": "Apache 2.0"
|
||||
},
|
||||
{
|
||||
"name": "attrs",
|
||||
"version": "26.1.0",
|
||||
"author": "Hynek Schlawack <hs@ox.cx>",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "audioop-lts",
|
||||
"version": "0.2.2",
|
||||
"author": "—",
|
||||
"license": "PSF-2.0"
|
||||
},
|
||||
{
|
||||
"name": "bcrypt",
|
||||
"version": "5.0.0",
|
||||
"author": "The Python Cryptographic Authority developers <cryptography-dev@python.org>",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
{
|
||||
"name": "cbor2",
|
||||
"version": "6.1.1",
|
||||
"author": "Alex Grönholm <alex.gronholm@nextday.fi>",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "cffi",
|
||||
"version": "2.0.0",
|
||||
"author": "Armin Rigo, Maciej Fijalkowski",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "cryptography",
|
||||
"version": "49.0.0",
|
||||
"author": "The Python Cryptographic Authority and individual contributors <cryptography-dev@python.org>",
|
||||
"license": "Apache-2.0 OR BSD-3-Clause"
|
||||
},
|
||||
{
|
||||
"name": "frozenlist",
|
||||
"version": "1.8.0",
|
||||
"author": "aiohttp team <team@aiohttp.org>",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
{
|
||||
"name": "idna",
|
||||
"version": "3.18",
|
||||
"author": "Kim Davies <kim+pypi@gumleaf.org>",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
{
|
||||
"name": "lxmf",
|
||||
"version": "1.0.1",
|
||||
"author": "Mark Qvist",
|
||||
"license": "Reticulum License"
|
||||
},
|
||||
{
|
||||
"name": "lxmfy",
|
||||
"version": "1.6.2",
|
||||
"author": "Quad4 <team@quad4.io>",
|
||||
"license": "BSD-0-Clause"
|
||||
},
|
||||
{
|
||||
"name": "lxst",
|
||||
"version": "0.4.8",
|
||||
"author": "Mark Qvist",
|
||||
"license": "Other/Proprietary License"
|
||||
},
|
||||
{
|
||||
"name": "miniaudio",
|
||||
"version": "1.71",
|
||||
"author": "Irmen de Jong <irmen@razorvine.net>",
|
||||
"license": "MIT"
|
||||
},
|
||||
{
|
||||
"name": "multidict",
|
||||
"version": "6.7.1",
|
||||
"author": "Andrew Svetlov",
|
||||
"license": "Apache License 2.0"
|
||||
},
|
||||
{
|
||||
"name": "numpy",
|
||||
"version": "2.4.6",
|
||||
"author": "Travis E. Oliphant et al.",
|
||||
"license": "BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0"
|
||||
},
|
||||
{
|
||||
"name": "ply",
|
||||
"version": "3.11",
|
||||
"author": "David Beazley",
|
||||
"license": "BSD"
|
||||
},
|
||||
{
|
||||
"name": "propcache",
|
||||
"version": "0.4.1",
|
||||
"author": "Andrew Svetlov",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
{
|
||||
"name": "psutil",
|
||||
"version": "7.2.2",
|
||||
"author": "Giampaolo Rodola",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
{
|
||||
"name": "pycodec2",
|
||||
"version": "4.1.1",
|
||||
"author": "Grzegorz Milka",
|
||||
"license": "OSI Approved :: BSD License"
|
||||
},
|
||||
{
|
||||
"name": "pycparser",
|
||||
"version": "3.0",
|
||||
"author": "Eli Bendersky <eliben@gmail.com>",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
{
|
||||
"name": "pyserial",
|
||||
"version": "3.5",
|
||||
"author": "Chris Liechti",
|
||||
"license": "BSD"
|
||||
},
|
||||
{
|
||||
"name": "reticulum-meshchatx",
|
||||
"version": "4.7.2",
|
||||
"author": "Quad4",
|
||||
"license": "0BSD AND MIT"
|
||||
},
|
||||
{
|
||||
"name": "rns",
|
||||
"version": "1.3.7",
|
||||
"author": "Mark Qvist",
|
||||
"license": "Reticulum License"
|
||||
},
|
||||
{
|
||||
"name": "websockets",
|
||||
"version": "16.0",
|
||||
"author": "Aymeric Augustin <aymeric.augustin@m4x.org>",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
{
|
||||
"name": "yarl",
|
||||
"version": "1.23.0",
|
||||
"author": "Andrew Svetlov",
|
||||
"license": "Apache-2.0"
|
||||
}
|
||||
]
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -21,6 +21,7 @@ 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"
|
||||
_BACKEND_LICENSES_FILENAME = "licenses_backend.json"
|
||||
_THIRD_PARTY_NOTICES_FILENAME = "THIRD_PARTY_NOTICES.txt"
|
||||
|
||||
|
||||
|
|
@ -186,7 +187,7 @@ def _merge_bundled_lxmfy(
|
|||
return merged
|
||||
|
||||
|
||||
def collect_backend_licenses() -> list[dict[str, Any]]:
|
||||
def _collect_backend_licenses_live() -> list[dict[str, Any]]:
|
||||
repo = _repo_root()
|
||||
for root in _ROOT_DIST_CANDIDATES:
|
||||
if _dist_for_requirement_name(root) is not None:
|
||||
|
|
@ -195,6 +196,30 @@ def collect_backend_licenses() -> list[dict[str, Any]]:
|
|||
return _merge_bundled_lxmfy(repo, _collect_python_transitive(roots))
|
||||
|
||||
|
||||
def _load_embedded_backend_licenses() -> list[dict[str, Any]] | None:
|
||||
for path in _embedded_data_paths(_BACKEND_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_backend_licenses() -> list[dict[str, Any]]:
|
||||
embedded = _load_embedded_backend_licenses()
|
||||
if getattr(sys, "frozen", False) and embedded:
|
||||
return embedded
|
||||
live = _collect_backend_licenses_live()
|
||||
if live:
|
||||
return live
|
||||
return embedded or []
|
||||
|
||||
|
||||
def _license_from_package_json(data: dict[str, Any]) -> str:
|
||||
lic = data.get("license")
|
||||
if isinstance(lic, str) and lic.strip():
|
||||
|
|
@ -497,11 +522,14 @@ def write_embedded_license_artifacts(repo_root: Path | None = None) -> dict[str,
|
|||
data_dir.mkdir(parents=True, exist_ok=True)
|
||||
payload = build_licenses_payload()
|
||||
frontend = payload.get("frontend", [])
|
||||
backend = payload.get("backend", [])
|
||||
meta = payload.get("meta", {}) if isinstance(payload, dict) else {}
|
||||
frontend_source = str(meta.get("frontend_source", "unknown"))
|
||||
frontend_path = data_dir / _FRONTEND_LICENSES_FILENAME
|
||||
backend_path = data_dir / _BACKEND_LICENSES_FILENAME
|
||||
notices_path = data_dir / _THIRD_PARTY_NOTICES_FILENAME
|
||||
frontend_rows = frontend if isinstance(frontend, list) else []
|
||||
backend_rows = backend if isinstance(backend, list) else []
|
||||
should_write_frontend = (
|
||||
bool(frontend_rows)
|
||||
or not frontend_path.exists()
|
||||
|
|
@ -516,11 +544,17 @@ def write_embedded_license_artifacts(repo_root: Path | None = None) -> dict[str,
|
|||
json.dumps(frontend_rows, indent=2, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
backend_path.write_text(
|
||||
json.dumps(backend_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,
|
||||
"backend_path": str(backend_path),
|
||||
"backend_count": len(backend_rows),
|
||||
"notices_path": str(notices_path),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ exclude = ["tests*"]
|
|||
namespaces = false
|
||||
|
||||
[tool.setuptools.package-data]
|
||||
meshchatx = ["public/**/*", "public/*", "src/backend/data/community_interfaces.json", "src/backend/data/licenses_frontend.json", "src/backend/data/THIRD_PARTY_NOTICES.txt", "src/frontend/public/repository-server-index.html"]
|
||||
meshchatx = ["public/**/*", "public/*", "src/backend/data/community_interfaces.json", "src/backend/data/licenses_frontend.json", "src/backend/data/licenses_backend.json", "src/backend/data/THIRD_PARTY_NOTICES.txt", "src/frontend/public/repository-server-index.html"]
|
||||
|
||||
[tool.setuptools.exclude-package-data]
|
||||
meshchatx = ["public/repository-server-bundled/**"]
|
||||
|
|
|
|||
|
|
@ -173,8 +173,13 @@ function shouldRefreshLicenseArtifacts(repoRoot) {
|
|||
const dataDir = path.join(repoRoot, "meshchatx", "src", "backend", "data");
|
||||
const noticesPath = path.join(dataDir, "THIRD_PARTY_NOTICES.txt");
|
||||
const frontendLicensesPath = path.join(dataDir, "licenses_frontend.json");
|
||||
const backendLicensesPath = path.join(dataDir, "licenses_backend.json");
|
||||
|
||||
const outputTimes = [fileMtimeMs(noticesPath), fileMtimeMs(frontendLicensesPath)];
|
||||
const outputTimes = [
|
||||
fileMtimeMs(noticesPath),
|
||||
fileMtimeMs(frontendLicensesPath),
|
||||
fileMtimeMs(backendLicensesPath),
|
||||
];
|
||||
if (outputTimes.some((t) => t == null)) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from meshchatx.src.backend.licenses_collector import (
|
|||
_filter_out_workspace_root_package,
|
||||
_flatten_pnpm_licenses_json,
|
||||
build_licenses_payload,
|
||||
collect_backend_licenses,
|
||||
collect_frontend_from_node_modules,
|
||||
render_third_party_notices,
|
||||
write_embedded_license_artifacts,
|
||||
|
|
@ -131,10 +132,13 @@ def test_write_embedded_license_artifacts_writes_files(tmp_path):
|
|||
result = write_embedded_license_artifacts(repo_root=tmp_path)
|
||||
|
||||
frontend_path = Path(result["frontend_path"])
|
||||
backend_path = Path(result["backend_path"])
|
||||
notices_path = Path(result["notices_path"])
|
||||
assert frontend_path.exists()
|
||||
assert backend_path.exists()
|
||||
assert notices_path.exists()
|
||||
assert '"name": "vue"' in frontend_path.read_text(encoding="utf-8")
|
||||
assert '"name": "rns"' in backend_path.read_text(encoding="utf-8")
|
||||
assert "Reticulum MeshChatX - Third-party notices" in notices_path.read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
|
|
@ -161,3 +165,40 @@ def test_write_embedded_license_artifacts_preserves_existing_frontend_when_empty
|
|||
|
||||
assert result["frontend_written"] is False
|
||||
assert frontend_path.read_text(encoding="utf-8") == '[{"name":"kept"}]\n'
|
||||
|
||||
|
||||
def test_collect_backend_licenses_uses_embedded_when_frozen():
|
||||
embedded = [{"name": "rns", "version": "1.0", "author": "a", "license": "MIT"}]
|
||||
with (
|
||||
patch("meshchatx.src.backend.licenses_collector._load_embedded_backend_licenses", return_value=embedded),
|
||||
patch("meshchatx.src.backend.licenses_collector._collect_backend_licenses_live", return_value=[]),
|
||||
patch("meshchatx.src.backend.licenses_collector.sys") as mock_sys,
|
||||
):
|
||||
mock_sys.frozen = True
|
||||
rows = collect_backend_licenses()
|
||||
assert rows == embedded
|
||||
|
||||
|
||||
def test_collect_backend_licenses_prefers_live_over_embedded_when_not_frozen():
|
||||
embedded = [{"name": "stale", "version": "0", "author": "a", "license": "MIT"}]
|
||||
live = [{"name": "rns", "version": "2.0", "author": "b", "license": "MIT"}]
|
||||
with (
|
||||
patch("meshchatx.src.backend.licenses_collector._load_embedded_backend_licenses", return_value=embedded),
|
||||
patch("meshchatx.src.backend.licenses_collector._collect_backend_licenses_live", return_value=live),
|
||||
patch("meshchatx.src.backend.licenses_collector.sys") as mock_sys,
|
||||
):
|
||||
mock_sys.frozen = False
|
||||
rows = collect_backend_licenses()
|
||||
assert rows == live
|
||||
|
||||
|
||||
def test_collect_backend_licenses_falls_back_to_embedded_when_live_empty():
|
||||
embedded = [{"name": "rns", "version": "1.0", "author": "a", "license": "MIT"}]
|
||||
with (
|
||||
patch("meshchatx.src.backend.licenses_collector._load_embedded_backend_licenses", return_value=embedded),
|
||||
patch("meshchatx.src.backend.licenses_collector._collect_backend_licenses_live", return_value=[]),
|
||||
patch("meshchatx.src.backend.licenses_collector.sys") as mock_sys,
|
||||
):
|
||||
mock_sys.frozen = False
|
||||
rows = collect_backend_licenses()
|
||||
assert rows == embedded
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue