appforge-cli 1.6.4__tar.gz → 1.6.6__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (23) hide show
  1. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/PKG-INFO +1 -1
  2. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/create.py +31 -0
  3. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/github.py +19 -10
  4. appforge_cli-1.6.6/appforge/injector.py +143 -0
  5. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/utils.py +1 -1
  6. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge_cli.egg-info/PKG-INFO +1 -1
  7. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/pyproject.toml +1 -1
  8. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/setup.py +1 -1
  9. appforge_cli-1.6.4/appforge/injector.py +0 -125
  10. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/__init__.py +0 -0
  11. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/ai.py +0 -0
  12. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/capacitor.py +0 -0
  13. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/cli.py +0 -0
  14. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/config.py +0 -0
  15. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/default_icon.png +0 -0
  16. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/detector.py +0 -0
  17. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge/knowledge_base.json +0 -0
  18. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge_cli.egg-info/SOURCES.txt +0 -0
  19. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge_cli.egg-info/dependency_links.txt +0 -0
  20. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge_cli.egg-info/entry_points.txt +0 -0
  21. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge_cli.egg-info/requires.txt +0 -0
  22. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/appforge_cli.egg-info/top_level.txt +0 -0
  23. {appforge_cli-1.6.4 → appforge_cli-1.6.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appforge-cli
3
- Version: 1.6.4
3
+ Version: 1.6.6
4
4
  Summary: Convert web, Flutter, and native apps into Android/iOS apps automatically using cloud builds.
5
5
  Author: AppForge Team
6
6
  Author-email: AppForge Team <juniorsir.bot@gmail.com>
@@ -60,6 +60,37 @@ def generate_html_template(project_name):
60
60
  with open(os.path.join(project_name, "www", "index.html"), "w") as f:
61
61
  f.write(html_content)
62
62
 
63
+ # --- Add this new helper function inside create.py ---
64
+ def sanitize_pubspec(project_dir):
65
+ """Removes monorepo-specific broken links from pubspec.yaml"""
66
+ pubspec_path = os.path.join(project_dir, "pubspec.yaml")
67
+ if not os.path.exists(pubspec_path):
68
+ return
69
+
70
+ with open(pubspec_path, "r", encoding='utf-8') as f:
71
+ lines = f.readlines()
72
+
73
+ new_lines = []
74
+ skip_next = False
75
+ for line in lines:
76
+ if skip_next:
77
+ skip_next = False
78
+ continue
79
+
80
+ # Remove the 'analysis_defaults' dependency and its 'path:' line
81
+ if "analysis_defaults:" in line:
82
+ skip_next = True # Skip this line and the next one (path: ...)
83
+ continue
84
+
85
+ # Remove the 'resolution: workspace' line if it exists
86
+ if "resolution: workspace" in line:
87
+ continue
88
+
89
+ new_lines.append(line)
90
+
91
+ with open(pubspec_path, "w", encoding='utf-8') as f:
92
+ f.writelines(new_lines)
93
+
63
94
  def create_project(framework, project_name):
64
95
  if framework not in TEMPLATES:
65
96
  print_error(f"Unknown framework: {framework}.")
@@ -22,8 +22,6 @@ def get_headers():
22
22
  "X-GitHub-Api-Version": "2022-11-28"
23
23
  }
24
24
 
25
- # --- Inside github.py ---
26
-
27
25
  def zip_project(category, sub_path=".", zip_name="app_source.zip"):
28
26
  """Zips the project, ensuring the icon is ready for @capacitor/assets."""
29
27
  config = load_local_config()
@@ -57,7 +55,6 @@ def zip_project(category, sub_path=".", zip_name="app_source.zip"):
57
55
 
58
56
  except Exception as e:
59
57
  print_error(f"Failed to prepare app assets for upload: {e}")
60
- # ------------------------------------
61
58
 
62
59
  def do_zip():
63
60
  original_dir = os.getcwd()
@@ -191,8 +188,8 @@ def push_and_build(config):
191
188
  "project_type": proj_type,
192
189
  "app_version": full_config.get("version", "1.0.0"),
193
190
  "sub_path": sub_path,
194
- "app_name": app_name, # <-- THESE NOW SEND "Vdl"
195
- "package_id": package_id, # <-- AND "com.appforge.vdl"
191
+ "app_name": app_name,
192
+ "package_id": package_id,
196
193
  "build_type": build_type
197
194
  }
198
195
  }
@@ -209,7 +206,6 @@ def push_and_build(config):
209
206
  from datetime import datetime
210
207
  project_name = os.path.basename(os.getcwd())
211
208
 
212
- # Create the history entry
213
209
  new_entry = {
214
210
  "app_id": app_id,
215
211
  "run_id": run_id,
@@ -222,6 +218,7 @@ def push_and_build(config):
222
218
  def check_status():
223
219
  """Streams live build logs from GitHub Actions with step-by-step animations."""
224
220
  headers = get_headers()
221
+ app_id = get_app_id()
225
222
  url = f"{GITHUB_API_URL}/repos/{BUILD_REPO_OWNER}/{BUILD_REPO_NAME}/actions/runs?per_page=1"
226
223
  res = requests.get(url, headers=headers)
227
224
 
@@ -230,14 +227,26 @@ def check_status():
230
227
  return
231
228
 
232
229
  runs = res.json().get("workflow_runs", [])
230
+ my_run = None
231
+ for run in runs:
232
+ if f"ID: {app_id}" in run.get("display_title", ""):
233
+ my_run = run
234
+ break
235
+
236
+ if not my_run:
237
+ print_info(f"No active builds found for your App ID ({app_id}).")
238
+ print(f"{GRAY}Note: New builds may take 10 seconds to appear in the list.{RESET}")
239
+ return
240
+
241
+ run_id = my_run["id"]
242
+ status = my_run["status"]
243
+
244
+ print_info(f"Connected to your Cloud Build #{run_id}. Streaming live logs...\n")
245
+
233
246
  if not runs:
234
247
  print_info("No builds found in the cloud yet.")
235
248
  return
236
249
 
237
- latest_run = runs[0]
238
- run_id = latest_run["id"]
239
- status = latest_run["status"]
240
-
241
250
  if status == "completed":
242
251
  conclusion = latest_run.get("conclusion")
243
252
  if conclusion == "success":
@@ -0,0 +1,143 @@
1
+ import os
2
+ import shutil
3
+ import re
4
+ from .utils import print_info, print_success, print_warning
5
+
6
+ def inject_metadata_locally(config):
7
+ """
8
+ Universally injects App Name, Package ID, Version, and Icons locally
9
+ for Android, iOS, Windows, and Linux before zipping.
10
+ """
11
+
12
+ app_name = config.get("appName", "AppForge App")
13
+ package_id = config.get("packageId", "com.appforge.app")
14
+ app_version = config.get("version", "1.0.0")
15
+ icon_path = config.get("iconPath")
16
+ web_dir = config.get("webDir", "dist")
17
+
18
+ # Calculate build number (e.g., 1.0.5 -> 10005)
19
+ try:
20
+ parts = app_version.split('.')
21
+ build_number = int(f"{parts[0]}{parts[1].zfill(2)}{parts[2].zfill(2)}")
22
+ except:
23
+ build_number = 1
24
+
25
+ # Slug for Binary Names (e.g., "Vault OS" -> "vault_os")
26
+ binary_slug = re.sub(r'[^a-zA-Z0-9]', '_', app_name).lower()
27
+
28
+ print_info("Injecting metadata locally before upload...")
29
+
30
+ # --- 1. ANDROID & CAPACITOR INJECTION ---
31
+ manifest_path = "app/src/main/AndroidManifest.xml" if os.path.exists("app/src/main/AndroidManifest.xml") else "android/app/src/main/AndroidManifest.xml"
32
+ gradle_groovy = "app/build.gradle" if os.path.exists("app/build.gradle") else "android/app/build.gradle"
33
+ gradle_kts = gradle_groovy + ".kts"
34
+
35
+ if os.path.exists(manifest_path):
36
+ with open(manifest_path, 'r', encoding='utf-8') as f:
37
+ manifest = f.read()
38
+ manifest = re.sub(r'android:label="[^"]+"', f'android:label="{app_name}"', manifest)
39
+ if 'xmlns:tools=' not in manifest:
40
+ manifest = manifest.replace('<manifest', '<manifest xmlns:tools="http://schemas.android.com/tools"')
41
+ if 'tools:replace="android:label"' not in manifest:
42
+ manifest = manifest.replace('<application', '<application tools:replace="android:label"')
43
+ if 'package=' in manifest:
44
+ manifest = re.sub(r'package="[^"]+"', f'package="{package_id}"', manifest)
45
+ with open(manifest_path, 'w', encoding='utf-8') as f:
46
+ f.write(manifest)
47
+ print_success(f"Injected Android Name: {app_name}")
48
+
49
+ target_gradle = gradle_kts if os.path.exists(gradle_kts) else (gradle_groovy if os.path.exists(gradle_groovy) else None)
50
+ if target_gradle:
51
+ with open(target_gradle, 'r', encoding='utf-8') as f:
52
+ gradle = f.read()
53
+ gradle = re.sub(r'applicationId\s*=?\s*["\'][^"\']+["\']', f'applicationId = "{package_id}"', gradle)
54
+ gradle = re.sub(r'namespace\s*=?\s*["\'][^"\']+["\']', f'namespace = "{package_id}"', gradle)
55
+ gradle = re.sub(r'versionName\s*=?\s*["\'][^"\']+["\']', f'versionName = "{app_version}"', gradle)
56
+ gradle = re.sub(r'versionCode\s*=?\s*\d+', f'versionCode = {build_number}', gradle)
57
+ with open(target_gradle, 'w', encoding='utf-8') as f:
58
+ f.write(gradle)
59
+ print_success(f"Injected Android Package ID: {package_id}")
60
+
61
+ # --- 2. WINDOWS DESKTOP INJECTION ---
62
+ win_cmake = "windows/CMakeLists.txt"
63
+ if os.path.exists(win_cmake):
64
+ with open(win_cmake, 'r', encoding='utf-8') as f:
65
+ content = f.read()
66
+ # Set project name and binary name
67
+ content = re.sub(r'set\(BINARY_NAME "[^"]+"\)', f'set(BINARY_NAME "{binary_slug}")', content)
68
+ content = re.sub(r'project\([^)]+\)', f'project({binary_slug} LANGUAGES CXX)', content)
69
+ with open(win_cmake, 'w', encoding='utf-8') as f:
70
+ f.write(content)
71
+
72
+ # Update Window Title in main.cpp
73
+ win_main = "windows/runner/main.cpp"
74
+ if os.path.exists(win_main):
75
+ with open(win_main, 'r', encoding='utf-8') as f:
76
+ cpp = f.read()
77
+ cpp = re.sub(r'window\.CreateAndShow\(L"[^"]+"', f'window.CreateAndShow(L"{app_name}"', cpp)
78
+ with open(win_main, 'w', encoding='utf-8') as f:
79
+ f.write(cpp)
80
+ print_success(f"Injected Windows Title: {app_name}")
81
+
82
+ # --- 3. LINUX DESKTOP INJECTION ---
83
+ linux_cmake = "linux/CMakeLists.txt"
84
+ if os.path.exists(linux_cmake):
85
+ with open(linux_cmake, 'r', encoding='utf-8') as f:
86
+ content = f.read()
87
+ content = re.sub(r'set\(BINARY_NAME "[^"]+"\)', f'set(BINARY_NAME "{binary_slug}")', content)
88
+ with open(linux_cmake, 'w', encoding='utf-8') as f:
89
+ f.write(content)
90
+
91
+ # Update Window Title in application.cc
92
+ # Usually found in linux/my_application.cc
93
+ for file in os.listdir("linux"):
94
+ if file.endswith(".cc"):
95
+ cc_path = os.path.join("linux", file)
96
+ with open(cc_path, 'r', encoding='utf-8') as f:
97
+ cc = f.read()
98
+ cc = re.sub(r'gtk_window_set_title\(GTK_WINDOW\(window\), "[^"]+"\)', f'gtk_window_set_title(GTK_WINDOW(window), "{app_name}")', cc)
99
+ with open(cc_path, 'w', encoding='utf-8') as f:
100
+ f.write(cc)
101
+ print_success(f"Injected Linux Title: {app_name}")
102
+
103
+ # --- 4. FLUTTER PUBSPEC INJECTION ---
104
+ if os.path.exists("pubspec.yaml"):
105
+ with open("pubspec.yaml", 'r', encoding='utf-8') as f:
106
+ pubspec = f.read()
107
+ pubspec = re.sub(r'^version:\s*.*$', f'version: {app_version}+{build_number}', pubspec, flags=re.MULTILINE)
108
+ pubspec = re.sub(r'^name:\s*.*$', f'name: {binary_slug}', pubspec, flags=re.MULTILINE)
109
+ with open("pubspec.yaml", 'w', encoding='utf-8') as f:
110
+ f.write(pubspec)
111
+ print_success(f"Updated pubspec.yaml version and name.")
112
+
113
+ # --- 5. UNIVERSAL ICON INJECTION ---
114
+ if icon_path and os.path.exists(icon_path):
115
+ injected_icons = False
116
+
117
+ # Android Mipmaps (Local)
118
+ if os.path.exists(manifest_path):
119
+ res_dir = os.path.join(os.path.dirname(manifest_path), 'res')
120
+ mipmaps = ['mipmap-mdpi', 'mipmap-hdpi', 'mipmap-xhdpi', 'mipmap-xxhdpi', 'mipmap-xxxhdpi']
121
+ for m in mipmaps:
122
+ target_dir = os.path.join(res_dir, m)
123
+ if os.path.exists(target_dir):
124
+ shutil.copy2(icon_path, os.path.join(target_dir, 'ic_launcher.png'))
125
+ if os.path.exists(os.path.join(target_dir, 'ic_launcher_round.png')):
126
+ shutil.copy2(icon_path, os.path.join(target_dir, 'ic_launcher_round.png'))
127
+ injected_icons = True
128
+
129
+ # Web/Flutter Web (Favicons & PWA)
130
+ web_targets = ["web", "public", web_dir]
131
+ for w_dir in web_targets:
132
+ if os.path.exists(w_dir):
133
+ shutil.copy2(icon_path, os.path.join(w_dir, 'favicon.png'))
134
+ icons_dir = os.path.join(w_dir, 'icons')
135
+ if os.path.exists(icons_dir):
136
+ for p_icon in ['Icon-192.png', 'Icon-512.png', 'Icon-maskable-192.png', 'Icon-maskable-512.png']:
137
+ shutil.copy2(icon_path, os.path.join(icons_dir, p_icon))
138
+ injected_icons = True
139
+
140
+ if injected_icons:
141
+ print_success("Injected App Icons across all detected platforms.")
142
+
143
+ print_success("Local Metadata Injection Complete.")
@@ -14,7 +14,7 @@ RED = "\033[31m"
14
14
  GRAY = "\033[90m"
15
15
  BLUE = "\033[34m"
16
16
 
17
- CLI_VERSION = "1.6.4"
17
+ CLI_VERSION = "1.6.6"
18
18
 
19
19
  def check_for_cli_updates():
20
20
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appforge-cli
3
- Version: 1.6.4
3
+ Version: 1.6.6
4
4
  Summary: Convert web, Flutter, and native apps into Android/iOS apps automatically using cloud builds.
5
5
  Author: AppForge Team
6
6
  Author-email: AppForge Team <juniorsir.bot@gmail.com>
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "appforge-cli"
7
- version = "1.6.4"
7
+ version = "1.6.6"
8
8
  authors = [
9
9
  { name="AppForge Team", email="juniorsir.bot@gmail.com" },
10
10
  ]
@@ -2,7 +2,7 @@ from setuptools import setup
2
2
 
3
3
  setup(
4
4
  name="appforge",
5
- version="1.6.4",
5
+ version="1.6.6",
6
6
  description="Convert web apps into Android apps automatically using Capacitor and GitHub cloud builds.",
7
7
  author="AppForge Team",
8
8
  packages=["appforge"],
@@ -1,125 +0,0 @@
1
- import os
2
- import shutil
3
- import re
4
- from .utils import print_info, print_success, print_warning
5
-
6
- def inject_metadata_locally(config):
7
- """Injects App Name, Package ID, Version, and Icons locally before zipping."""
8
-
9
- app_name = config.get("appName", "AppForge App")
10
- package_id = config.get("packageId", "com.appforge.app")
11
- app_version = config.get("version", "1.0.0")
12
- icon_path = config.get("iconPath")
13
- web_dir = config.get("webDir", "dist")
14
- project_category = config.get("category", "web")
15
-
16
- try:
17
- parts = app_version.split('.')
18
- build_number = int(f"{parts[0]}{parts[1].zfill(2)}{parts[2].zfill(2)}")
19
- except:
20
- build_number = 1
21
-
22
- print_info("Injecting metadata locally before upload...")
23
-
24
- # --- PATH DETECTION ---
25
- manifest_path = "app/src/main/AndroidManifest.xml" if os.path.exists("app/src/main/AndroidManifest.xml") else "android/app/src/main/AndroidManifest.xml"
26
- gradle_groovy = "app/build.gradle" if os.path.exists("app/build.gradle") else "android/app/build.gradle"
27
- gradle_kts = gradle_groovy + ".kts"
28
-
29
- # --- 1. INJECT APP NAME (AndroidManifest.xml) ---
30
- if os.path.exists(manifest_path):
31
- with open(manifest_path, 'r', encoding='utf-8') as f:
32
- manifest = f.read()
33
-
34
- manifest = re.sub(r'android:label="[^"]+"', f'android:label="{app_name}"', manifest)
35
- if 'xmlns:tools=' not in manifest:
36
- manifest = manifest.replace('<manifest', '<manifest xmlns:tools="http://schemas.android.com/tools"')
37
- if 'tools:replace="android:label"' not in manifest:
38
- manifest = manifest.replace('<application', '<application tools:replace="android:label"')
39
- if 'package=' in manifest:
40
- manifest = re.sub(r'package="[^"]+"', f'package="{package_id}"', manifest)
41
-
42
- with open(manifest_path, 'w', encoding='utf-8') as f:
43
- f.write(manifest)
44
- print_success(f"Injected App Name: {app_name}")
45
-
46
- # --- 2. INJECT PACKAGE ID & VERSION (build.gradle) ---
47
- target_gradle = gradle_kts if os.path.exists(gradle_kts) else (gradle_groovy if os.path.exists(gradle_groovy) else None)
48
-
49
- if target_gradle:
50
- with open(target_gradle, 'r', encoding='utf-8') as f:
51
- gradle = f.read()
52
-
53
- gradle = re.sub(r'applicationId\s*=?\s*["\'][^"\']+["\']', f'applicationId = "{package_id}"', gradle)
54
- gradle = re.sub(r'applicationId\s+["\'][^"\']+["\']', f'applicationId "{package_id}"', gradle)
55
- gradle = re.sub(r'namespace\s*=?\s*["\'][^"\']+["\']', f'namespace = "{package_id}"', gradle)
56
- gradle = re.sub(r'namespace\s+["\'][^"\']+["\']', f'namespace "{package_id}"', gradle)
57
- gradle = re.sub(r'versionName\s*=?\s*["\'][^"\']+["\']', f'versionName = "{app_version}"', gradle)
58
- gradle = re.sub(r'versionName\s+["\'][^"\']+["\']', f'versionName "{app_version}"', gradle)
59
- gradle = re.sub(r'versionCode\s*=?\s*\d+', f'versionCode = {build_number}', gradle)
60
- gradle = re.sub(r'versionCode\s+\d+', f'versionCode {build_number}', gradle)
61
-
62
- with open(target_gradle, 'w', encoding='utf-8') as f:
63
- f.write(gradle)
64
- print_success(f"Injected Package ID: {package_id} & Version: {app_version}")
65
-
66
- # --- 3. INJECT PUBSPEC.YAML (Flutter Specifics) ---
67
- if os.path.exists("pubspec.yaml"):
68
- with open("pubspec.yaml", 'r', encoding='utf-8') as f:
69
- pubspec = f.read()
70
-
71
- # 3a. Update Version
72
- if re.search(r'^version:\s*.*$', pubspec, re.MULTILINE):
73
- pubspec = re.sub(r'^version:\s*.*$', f'version: {app_version}+{build_number}', pubspec, flags=re.MULTILINE)
74
-
75
- # 3b. Update Project Name (Must be snake_case for Dart!)
76
- # e.g. "Vault Downloader" -> "vault_downloader"
77
- snake_case_name = re.sub(r'[^a-zA-Z0-9]', '_', app_name).lower()
78
- if re.search(r'^name:\s*.*$', pubspec, re.MULTILINE):
79
- pubspec = re.sub(r'^name:\s*.*$', f'name: {snake_case_name}', pubspec, flags=re.MULTILINE)
80
-
81
- with open("pubspec.yaml", 'w', encoding='utf-8') as f:
82
- f.write(pubspec)
83
- print_success(f"Updated pubspec.yaml (name: {snake_case_name}, version: {app_version})")
84
-
85
- # --- 4. INJECT APP ICONS (Universal) ---
86
- if icon_path and os.path.exists(icon_path):
87
- injected_icons = False
88
-
89
- # 4a. Android Mipmaps
90
- if os.path.exists(manifest_path):
91
- res_dir = os.path.join(os.path.dirname(manifest_path), 'res')
92
- mipmaps = ['mipmap-mdpi', 'mipmap-hdpi', 'mipmap-xhdpi', 'mipmap-xxhdpi', 'mipmap-xxxhdpi']
93
- for m in mipmaps:
94
- target_dir = os.path.join(res_dir, m)
95
- if os.path.exists(target_dir):
96
- shutil.copyfile(icon_path, os.path.join(target_dir, 'ic_launcher.png'))
97
- round_icon = os.path.join(target_dir, 'ic_launcher_round.png')
98
- if os.path.exists(round_icon):
99
- shutil.copyfile(icon_path, round_icon)
100
- injected_icons = True
101
-
102
- # 4b. Web / Flutter Web Icons (PWA Support)
103
- # Check standard web directories
104
- web_targets = ["web", "public", web_dir]
105
- for w_dir in web_targets:
106
- if os.path.exists(w_dir):
107
- # Replace Favicon
108
- shutil.copyfile(icon_path, os.path.join(w_dir, 'favicon.png'))
109
- if os.path.exists(os.path.join(w_dir, 'favicon.ico')):
110
- shutil.copyfile(icon_path, os.path.join(w_dir, 'favicon.ico'))
111
-
112
- # Replace PWA Icons
113
- icons_dir = os.path.join(w_dir, 'icons')
114
- if os.path.exists(icons_dir):
115
- pwa_icons = ['Icon-192.png', 'Icon-512.png', 'Icon-maskable-192.png', 'Icon-maskable-512.png']
116
- for p_icon in pwa_icons:
117
- shutil.copyfile(icon_path, os.path.join(icons_dir, p_icon))
118
- injected_icons = True
119
-
120
- if injected_icons:
121
- print_success("Injected custom App Icons across Native and Web directories.")
122
- else:
123
- print_warning("Icon path found, but no valid target directories (res/mipmap or web/icons) exist yet.")
124
-
125
- print_success("Local Metadata Injection Complete. Project is ready for upload.")
File without changes