""" migrate_template.py ------------------- End-to-end migration: downloads an Adobe Sign template, converts it to DocuSign format, and uploads it to DocuSign. Usage: python3 src/migrate_template.py --list Show all Adobe Sign templates available to download. python3 src/migrate_template.py --template "Template Name" Download, convert, and upload the named template. If multiple templates share the same name, the most recently modified one is used. python3 src/migrate_template.py --template "Template Name" --skip-upload Download and convert only — writes the DocuSign JSON to migration-output/ without uploading. """ import argparse import os import sys from pathlib import Path from dotenv import load_dotenv load_dotenv() sys.path.insert(0, os.path.dirname(__file__)) from adobe_api import adobe_api_get, adobe_api_get_bytes from compose_docusign_template import compose_template from upload_docusign_template import upload_template DOWNLOADS_DIR = Path(__file__).parent.parent / "downloads" OUTPUT_DIR = Path(__file__).parent.parent / "migration-output" def safe_dirname(name): return "".join(c if c.isalnum() or c in " -_" else "_" for c in name).strip() def save_json(path, data): import json with open(path, "w", encoding="utf-8") as f: json.dump(data, f, indent=2) def fetch_template_list(): result = adobe_api_get("libraryDocuments") return result.get("libraryDocumentList", []) def cmd_list(): print("Fetching template list from Adobe Sign...") templates = fetch_template_list() if not templates: print("No templates found.") return print(f"\n{'Name':<45} {'Modified':<25} {'ID'}") print("-" * 100) for t in sorted(templates, key=lambda x: x.get("modifiedDate", ""), reverse=True): print(f"{t['name']:<45} {t.get('modifiedDate', 'n/a'):<25} {t['id']}") def find_template(template_name, templates): matches = [t for t in templates if t["name"] == template_name] if not matches: print(f"ERROR: No template named '{template_name}' found in Adobe Sign.") print("Run --list to see available templates.") sys.exit(1) if len(matches) > 1: print(f" {len(matches)} templates named '{template_name}' — using most recently modified.") return max(matches, key=lambda t: t.get("modifiedDate", "")) def download_template(template) -> Path: import json template_id = template["id"] template_name = template["name"] dir_name = f"{safe_dirname(template_name)}__{template_id[:8]}" out_dir = DOWNLOADS_DIR / dir_name out_dir.mkdir(parents=True, exist_ok=True) print(f"\nDownloading '{template_name}' → downloads/{dir_name}/") metadata = adobe_api_get(f"libraryDocuments/{template_id}") save_json(out_dir / "metadata.json", metadata) try: form_fields = adobe_api_get(f"libraryDocuments/{template_id}/formFields") save_json(out_dir / "form_fields.json", form_fields) field_count = len(form_fields.get("fields", [])) print(f" {field_count} form fields") except Exception as e: print(f" WARNING: Could not fetch form fields: {e}") save_json(out_dir / "form_fields.json", {"error": str(e)}) docs = adobe_api_get(f"libraryDocuments/{template_id}/documents") save_json(out_dir / "documents.json", docs) for doc in docs.get("documents", []): doc_id = doc["id"] doc_name = doc.get("name", doc_id) if not doc_name.lower().endswith(".pdf"): doc_name += ".pdf" safe_name = safe_dirname(doc_name) try: pdf_bytes = adobe_api_get_bytes( f"libraryDocuments/{template_id}/documents/{doc_id}" ) with open(out_dir / safe_name, "wb") as f: f.write(pdf_bytes) print(f" PDF ({len(pdf_bytes) // 1024}KB) → {safe_name}") except Exception as e: print(f" WARNING: Could not download PDF: {e}") return out_dir def convert_template(template_dir: Path) -> Path: output_path = OUTPUT_DIR / template_dir.name / "docusign-template.json" print(f"\nConverting to DocuSign format...") _, warnings, _ = compose_template(str(template_dir), str(output_path)) print(f" Written: {output_path}") for w in warnings: print(f" WARNING: {w}") return output_path def main(): parser = argparse.ArgumentParser( description="Migrate an Adobe Sign template to DocuSign", formatter_class=argparse.RawDescriptionHelpFormatter, epilog=__doc__, ) group = parser.add_mutually_exclusive_group(required=True) group.add_argument( "--template", metavar="NAME", help="Name of the Adobe Sign template to migrate" ) group.add_argument( "--list", action="store_true", help="List available Adobe Sign templates" ) parser.add_argument( "--skip-upload", action="store_true", help="Convert only — do not upload to DocuSign" ) args = parser.parse_args() DOWNLOADS_DIR.mkdir(exist_ok=True) OUTPUT_DIR.mkdir(exist_ok=True) if args.list: cmd_list() return templates = fetch_template_list() template = find_template(args.template, templates) template_dir = download_template(template) output_path = convert_template(template_dir) if args.skip_upload: print(f"\nSkipped upload. DocuSign JSON: {output_path}") else: print() upload_template(str(output_path)) if __name__ == "__main__": main()