Primer commit
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Audit script compliance with MP custom-field safety rules."""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
SCRIPTS_DIR = os.path.join(ROOT_DIR, "scripts")
|
||||
|
||||
SKIP_FILES = {"common.py", "audit_script_compliance.py"}
|
||||
|
||||
|
||||
def read_text(path):
|
||||
with open(path, "r", encoding="utf-8", errors="replace") as fh:
|
||||
return fh.read()
|
||||
|
||||
|
||||
def line_number(text, index):
|
||||
return text.count("\n", 0, index) + 1
|
||||
|
||||
|
||||
def line_at(text, index):
|
||||
start = text.rfind("\n", 0, index) + 1
|
||||
end = text.find("\n", index)
|
||||
if end == -1:
|
||||
end = len(text)
|
||||
return text[start:end]
|
||||
|
||||
|
||||
def find_issues(path):
|
||||
text = read_text(path)
|
||||
issues = []
|
||||
uses_custom_fields = bool(re.search(r"customFields|custom_fields_json|custom_fields|field_id|fieldId", text))
|
||||
mutates_custom_fields = bool(re.search(r"update_contact|create_contact|update_opportunity|create_opportunity|\"PUT\"|\"POST\"", text)) and "customFields" in text
|
||||
schema_calls = bool(re.search(r"get_object_schema|get_schemas|/objects/", text))
|
||||
|
||||
if uses_custom_fields and not schema_calls:
|
||||
issues.append((1, "Lee o compara custom fields sin resolver schema dinamico en el script."))
|
||||
|
||||
for match in re.finditer(r"[furbFURB]?[\"']https://services\.leadconnectorhq\.com/objects/[^\"']+|[furbFURB]?[\"']/objects/\{[^}]+\}|[furbFURB]?[\"']/objects/\w+", text):
|
||||
prefix = text[max(0, match.start() - 600):match.start()]
|
||||
if "/objects/" not in prefix and "get_objects_catalog" not in prefix:
|
||||
issues.append((line_number(text, match.start()), "Posible llamada a schema de objeto sin consulta previa al catalogo /objects/."))
|
||||
|
||||
for match in re.finditer(r"customFields\s*[:=]\s*\{", text):
|
||||
issues.append((line_number(text, match.start()), "customFields parece construirse como diccionario; GHL espera lista de {id, value}."))
|
||||
|
||||
for match in re.finditer(r"\{\s*[\"']id[\"']\s*:\s*[\"'][A-Za-z0-9_-]{8,}[\"']", text):
|
||||
if line_at(text, match.start()).strip().startswith("#"):
|
||||
continue
|
||||
issues.append((line_number(text, match.start()), "Posible ID hardcodeado en payload de custom field."))
|
||||
|
||||
if mutates_custom_fields and "--dry-run" not in text and "--apply" not in text:
|
||||
issues.append((1, "Script mutador de custom fields sin --dry-run o --apply evidente."))
|
||||
|
||||
return sorted(set(issues))
|
||||
|
||||
|
||||
def audit_scripts():
|
||||
results = []
|
||||
for filename in sorted(os.listdir(SCRIPTS_DIR)):
|
||||
if not filename.endswith(".py") or filename in SKIP_FILES:
|
||||
continue
|
||||
path = os.path.join(SCRIPTS_DIR, filename)
|
||||
if not os.path.isfile(path):
|
||||
continue
|
||||
issues = find_issues(path)
|
||||
results.append((filename, issues))
|
||||
return results
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Audita cumplimiento tecnico de scripts MP.")
|
||||
parser.add_argument("--fail-on-issues", action="store_true", help="Retorna codigo 1 si encuentra hallazgos.")
|
||||
args = parser.parse_args()
|
||||
|
||||
results = audit_scripts()
|
||||
issue_count = sum(len(issues) for _, issues in results)
|
||||
print("=== AUDITORIA DE CUMPLIMIENTO DE SCRIPTS ===")
|
||||
print(f"Scripts auditados: {len(results)}")
|
||||
print(f"Hallazgos: {issue_count}\n")
|
||||
|
||||
for filename, issues in results:
|
||||
status = "OK" if not issues else "REVISAR"
|
||||
print(f"[{status}] {filename}")
|
||||
for line, message in issues:
|
||||
print(f" L{line}: {message}")
|
||||
if issue_count and args.fail_on_issues:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user