File size: 5,296 Bytes
490ec84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#!/usr/bin/env python3
import os
import re
import shutil
import subprocess
from datetime import datetime
import json

# --- CONFIGURATION: Define update rules ---
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/"
        }
    }
}

# --- MAIN UPDATE HANDLERS ---
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

# --- MAIN EXECUTION ---
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.")