|
|
|
|
|
import os |
|
|
import re |
|
|
import shutil |
|
|
import subprocess |
|
|
from datetime import datetime |
|
|
import json |
|
|
|
|
|
|
|
|
UPDATES = { |
|
|
"requirements.txt": { |
|
|
"type": "lines", |
|
|
"entries": [ |
|
|
"fastapi", |
|
|
"gunicorn", |
|
|
"uvicorn", |
|
|
"flask", |
|
|
"pandas==2.1.4", |
|
|
"numpy==1.25.2", |
|
|
"python-dotenv" |
|
|
], |
|
|
"comment": "# Required for agentic services" |
|
|
}, |
|
|
".env": { |
|
|
"type": "keyval", |
|
|
"entries": { |
|
|
"STATIC_FILES_DIR": "/app/static", |
|
|
"FLASK_APP": "backend.app", |
|
|
"FLASK_ENV": "production" |
|
|
} |
|
|
}, |
|
|
"Dockerfile": { |
|
|
"type": "replace_section", |
|
|
"pattern": r"^CMD\s+.*", |
|
|
"replacement": 'CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "backend.app:app"]' |
|
|
}, |
|
|
"angular.json": { |
|
|
"type": "json_property", |
|
|
"path": "projects.agentic-dashboard.architect.build.options", |
|
|
"updates": { |
|
|
"outputPath": "dist", |
|
|
"baseHref": "/static/" |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
class FileUpdater: |
|
|
@staticmethod |
|
|
def backup_file(filepath): |
|
|
if not os.path.exists(filepath): |
|
|
return None |
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") |
|
|
backup_path = f"{filepath}.bak_{timestamp}" |
|
|
shutil.copy2(filepath, backup_path) |
|
|
return backup_path |
|
|
|
|
|
@staticmethod |
|
|
def update_lines_file(path, entries, comment=None): |
|
|
existing = set() |
|
|
if os.path.exists(path): |
|
|
with open(path, "r") as f: |
|
|
existing = {line.strip().lower() for line in f.readlines() if line.strip()} |
|
|
|
|
|
needed = [e for e in entries if e.lower() not in existing] |
|
|
if not needed: |
|
|
return False, None |
|
|
|
|
|
backup = FileUpdater.backup_file(path) |
|
|
with open(path, "a") as f: |
|
|
if comment: |
|
|
f.write(f"\n{comment}\n") |
|
|
for entry in needed: |
|
|
f.write(f"{entry}\n") |
|
|
return True, backup |
|
|
|
|
|
@staticmethod |
|
|
def update_keyval_file(path, kvs): |
|
|
existing = {} |
|
|
if os.path.exists(path): |
|
|
with open(path, "r") as f: |
|
|
for line in f: |
|
|
if "=" in line and not line.startswith("#"): |
|
|
k, v = line.strip().split("=", 1) |
|
|
existing[k.strip()] = v.strip() |
|
|
needs_update = any(existing.get(k) != v for k, v in kvs.items()) |
|
|
if not needs_update: |
|
|
return False, None |
|
|
|
|
|
backup = FileUpdater.backup_file(path) |
|
|
with open(path, "w") as f: |
|
|
for k, v in kvs.items(): |
|
|
f.write(f"{k}={v}\n") |
|
|
for k, v in existing.items(): |
|
|
if k not in kvs: |
|
|
f.write(f"{k}={v}\n") |
|
|
return True, backup |
|
|
|
|
|
@staticmethod |
|
|
def update_regex_file(path, pattern, replacement): |
|
|
if not os.path.exists(path): |
|
|
return False, None |
|
|
with open(path, "r") as f: |
|
|
content = f.read() |
|
|
updated = re.sub(pattern, replacement, content, flags=re.MULTILINE) |
|
|
if content == updated: |
|
|
return False, None |
|
|
backup = FileUpdater.backup_file(path) |
|
|
with open(path, "w") as f: |
|
|
f.write(updated) |
|
|
return True, backup |
|
|
|
|
|
@staticmethod |
|
|
def update_json_file(path, json_path, updates): |
|
|
if not os.path.exists(path): |
|
|
return False, None |
|
|
backup = FileUpdater.backup_file(path) |
|
|
with open(path, "r") as f: |
|
|
data = json.load(f) |
|
|
keys = json_path.split(".") |
|
|
ref = data |
|
|
for k in keys[:-1]: |
|
|
ref = ref.setdefault(k, {}) |
|
|
ref[keys[-1]] = {**ref.get(keys[-1], {}), **updates} |
|
|
with open(path, "w") as f: |
|
|
json.dump(data, f, indent=2) |
|
|
return True, backup |
|
|
|
|
|
|
|
|
def smart_append(base_dir="."): |
|
|
print(f"\n🔧 Scanning: {os.path.abspath(base_dir)}") |
|
|
for filename, rule in UPDATES.items(): |
|
|
path = os.path.join(base_dir, filename) |
|
|
print(f"\n⚙️ Updating: {filename}") |
|
|
try: |
|
|
if rule["type"] == "lines": |
|
|
updated, backup = FileUpdater.update_lines_file(path, rule["entries"], rule.get("comment")) |
|
|
elif rule["type"] == "keyval": |
|
|
updated, backup = FileUpdater.update_keyval_file(path, rule["entries"]) |
|
|
elif rule["type"] == "replace_section": |
|
|
updated, backup = FileUpdater.update_regex_file(path, rule["pattern"], rule["replacement"]) |
|
|
elif rule["type"] == "json_property": |
|
|
updated, backup = FileUpdater.update_json_file(path, rule["path"], rule["updates"]) |
|
|
else: |
|
|
print("⚠️ Unknown update type.") |
|
|
continue |
|
|
if updated: |
|
|
print(f"✅ Updated {filename} (Backup: {backup})") |
|
|
else: |
|
|
print(f"✓ No changes needed") |
|
|
except Exception as e: |
|
|
print(f"❌ Error: {e}") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
import sys |
|
|
directory = sys.argv[1] if len(sys.argv) > 1 else "." |
|
|
smart_append(directory) |
|
|
print("\n✨ All updates complete.") |
|
|
|