Files
upstream-activitywatch/scripts/patch_research_edition_export.py
upstream-mirror c5c5af8cde
Some checks failed
Research feed publisher tests / test (push) Failing after 3s
Release / Pre-flight checks (push) Failing after 0s
Release / Research Edition — patch and packaging smoke tests (push) Has been skipped
Release / Build Qt artifacts (false, 22, ubuntu-22.04, 3.9, ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) }}, false, false) (push) Has been skipped
Release / Build Qt artifacts (false, 22, ubuntu-22.04, 3.9, false, false, false) (push) Has been skipped
Release / Build Qt artifacts (manylinux_2_28 — glibc 2.28 ABI floor) (22, 3.9) (push) Has been skipped
Release / Generate release notes (push) Has been skipped
Release / Create dev release tag (push) Has been skipped
CodeQL / Analyze (javascript) (push) Failing after 1s
CodeQL / Analyze (python) (push) Failing after 1s
Release / Build Qt artifacts (false, 22, windows-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, macos-15-intel, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, macos-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04, 3.9, ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) }}, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04-arm, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, windows-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Qt artifacts (false, 22, macos-15-intel, 3.9, false, false, false) (push) Has been cancelled
Release / Build Qt artifacts (false, 22, macos-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Publish draft release (push) Has been cancelled
snapshot of https://github.com/ActivityWatch/activitywatch.git (master, 2026-09-15; blobs > 131072B omitted)
2026-09-15 22:29:06 +05:00

113 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Patch aw-server-rust export endpoints for the Research Edition build.
Run as part of the CI build for research edition:
python3 scripts/patch_research_edition_export.py [repo-root]
Standard builds never run this script, so `/api/0/export` stays byte-for-byte
unchanged outside Research Edition. The patch is fail-closed: missing markers
abort the build rather than shipping an unsanitized artifact.
"""
from __future__ import annotations
import pathlib
import shutil
import sys
MARKER = "RESEARCH_EDITION_EXPORT_SANITIZE"
EXPORT_INSERT_NEEDLE = """ export.buckets.insert(bid, bucket);
}
Ok(export.into())
"""
EXPORT_INSERT_REPLACEMENT = f""" export.buckets.insert(bid, bucket);
}}
// {MARKER}
let export = match super::export_sanitize::sanitize_buckets_export(export) {{
Ok(export) => export,
Err(err) => {{
return Err(HttpErrorJson::new(rocket::http::Status::Conflict, err))
}}
}};
Ok(export.into())
"""
BUCKET_INSERT_NEEDLE = """ export.buckets.insert(bucket_id.into(), bucket);
Ok(export.into())
"""
BUCKET_INSERT_REPLACEMENT = f""" export.buckets.insert(bucket_id.into(), bucket);
// {MARKER}
let export = match super::export_sanitize::sanitize_buckets_export(export) {{
Ok(export) => export,
Err(err) => return Err(HttpErrorJson::new(Status::Conflict, err)),
}};
Ok(export.into())
"""
MOD_NEEDLE = "mod export;\n"
MOD_REPLACEMENT = "mod export;\nmod export_sanitize;\n"
def repo_root_from_args(argv: list[str]) -> pathlib.Path:
if len(argv) > 1:
return pathlib.Path(argv[1]).resolve()
return pathlib.Path.cwd().resolve()
def _replace_once(path: pathlib.Path, needle: str, replacement: str, already_ok: str) -> None:
text = path.read_text(encoding="utf-8")
if already_ok in text:
return
count = text.count(needle)
if count != 1:
raise ValueError(
f"{path}: expected exactly one export-sanitizer insertion point, found {count}"
)
path.write_text(text.replace(needle, replacement, 1), encoding="utf-8")
def patch_tree(repo_root: pathlib.Path) -> None:
script_dir = pathlib.Path(__file__).resolve().parent
source = script_dir / "research_edition" / "export_sanitize.rs"
if not source.is_file():
raise FileNotFoundError(f"missing sanitizer module: {source}")
endpoints = (
repo_root / "aw-server-rust" / "aw-server" / "src" / "endpoints"
)
export_rs = endpoints / "export.rs"
bucket_rs = endpoints / "bucket.rs"
mod_rs = endpoints / "mod.rs"
dest = endpoints / "export_sanitize.rs"
for required in (export_rs, bucket_rs, mod_rs):
if not required.is_file():
raise FileNotFoundError(f"expected Rust export source at {required}")
shutil.copyfile(source, dest)
_replace_once(mod_rs, MOD_NEEDLE, MOD_REPLACEMENT, "mod export_sanitize;")
_replace_once(export_rs, EXPORT_INSERT_NEEDLE, EXPORT_INSERT_REPLACEMENT, MARKER)
_replace_once(bucket_rs, BUCKET_INSERT_NEEDLE, BUCKET_INSERT_REPLACEMENT, MARKER)
def main() -> None:
repo_root = repo_root_from_args(sys.argv)
try:
patch_tree(repo_root)
except (OSError, ValueError) as error:
print(f"Error: {error}", file=sys.stderr)
sys.exit(1)
print(f"Patched Research Edition export sanitizer under {repo_root / 'aw-server-rust'}")
if __name__ == "__main__":
main()