- id-based incremental (new_ids - prev_integrated_ids) with --full flag, kindle-id tag, deletions handling - Mappings tab with delete to return to Conflicts, sorted by updated_at newest first - Integrated tab with Added On / Integrated dates and Citekey - black over white iA Writer Duo theme, candidate-list per-row Use buttons, resizable/sortable tables - Settings editable + save, Artifacts Open/Reveal, Re-import bar with last file reuse - Fix override single-field validation and ignore cleanup
74 lines
2.4 KiB
Python
74 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import zipfile
|
|
from pathlib import Path
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
PLUGIN_DIR = ROOT / "plugin"
|
|
DIST_DIR = ROOT / "dist"
|
|
OUTPUT = DIST_DIR / "kindle-zotero-importer.xpi"
|
|
|
|
|
|
def main() -> int:
|
|
DIST_DIR.mkdir(exist_ok=True)
|
|
if OUTPUT.exists():
|
|
OUTPUT.unlink()
|
|
|
|
manifest_path = PLUGIN_DIR / "manifest.json"
|
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
zotero_app = manifest.get("applications", {}).get("zotero", {})
|
|
required_manifest_fields = [
|
|
"manifest_version",
|
|
"name",
|
|
"version",
|
|
"description",
|
|
]
|
|
missing_manifest_fields = [
|
|
field for field in required_manifest_fields if not manifest.get(field)
|
|
]
|
|
missing_zotero_fields = [
|
|
field
|
|
for field in ["id", "strict_min_version", "strict_max_version"]
|
|
if not zotero_app.get(field)
|
|
]
|
|
missing_files = [
|
|
str(path.relative_to(PLUGIN_DIR))
|
|
for path in [PLUGIN_DIR / "manifest.json", PLUGIN_DIR / "bootstrap.js"]
|
|
if not path.exists()
|
|
]
|
|
if missing_manifest_fields or missing_zotero_fields or missing_files:
|
|
problems = []
|
|
if missing_manifest_fields:
|
|
problems.append(f"manifest fields: {', '.join(missing_manifest_fields)}")
|
|
if missing_zotero_fields:
|
|
problems.append(
|
|
f"applications.zotero fields: {', '.join(missing_zotero_fields)}"
|
|
)
|
|
if missing_files:
|
|
problems.append(f"files: {', '.join(missing_files)}")
|
|
raise SystemExit("Invalid plugin package; missing " + "; ".join(problems))
|
|
|
|
with zipfile.ZipFile(OUTPUT, "w", zipfile.ZIP_DEFLATED) as archive:
|
|
for path in PLUGIN_DIR.rglob("*"):
|
|
if path.is_file():
|
|
archive_name = str(path.relative_to(PLUGIN_DIR))
|
|
info = zipfile.ZipInfo(archive_name)
|
|
info.create_system = 3
|
|
info.external_attr = 0o644 << 16
|
|
archive.writestr(info, path.read_bytes(), zipfile.ZIP_DEFLATED)
|
|
|
|
with zipfile.ZipFile(OUTPUT) as archive:
|
|
names = set(archive.namelist())
|
|
if "manifest.json" not in names or "bootstrap.js" not in names:
|
|
raise SystemExit(
|
|
"Invalid XPI: manifest.json and bootstrap.js must be at archive root"
|
|
)
|
|
|
|
print(f"Built {OUTPUT}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|