appforge-cli 1.6.2__tar.gz → 1.6.4__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.2 → appforge_cli-1.6.4}/PKG-INFO +1 -1
  2. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/cli.py +27 -28
  3. appforge_cli-1.6.4/appforge/create.py +165 -0
  4. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/utils.py +1 -1
  5. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge_cli.egg-info/PKG-INFO +1 -1
  6. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/pyproject.toml +1 -1
  7. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/setup.py +1 -1
  8. appforge_cli-1.6.2/appforge/create.py +0 -106
  9. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/__init__.py +0 -0
  10. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/ai.py +0 -0
  11. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/capacitor.py +0 -0
  12. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/config.py +0 -0
  13. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/default_icon.png +0 -0
  14. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/detector.py +0 -0
  15. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/github.py +0 -0
  16. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/injector.py +0 -0
  17. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge/knowledge_base.json +0 -0
  18. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge_cli.egg-info/SOURCES.txt +0 -0
  19. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge_cli.egg-info/dependency_links.txt +0 -0
  20. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge_cli.egg-info/entry_points.txt +0 -0
  21. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge_cli.egg-info/requires.txt +0 -0
  22. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/appforge_cli.egg-info/top_level.txt +0 -0
  23. {appforge_cli-1.6.2 → appforge_cli-1.6.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: appforge-cli
3
- Version: 1.6.2
3
+ Version: 1.6.4
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>
@@ -15,30 +15,34 @@ from .capacitor import install_plugins
15
15
  from .create import create_project
16
16
 
17
17
  def init_project():
18
+ # 1. Load existing config first to see if 'create' already set things up
19
+ current_config = load_local_config()
20
+
18
21
  project_info = detect_project()
19
22
  category = project_info.get("category", "web")
20
23
  proj_type = project_info.get("type", "unknown")
21
24
  default_icon_dest = os.path.join(os.getcwd(), "appforge_icon.png")
22
25
 
23
- current_config = load_local_config()
24
26
  if not current_config.get("iconPath") or not os.path.exists(current_config.get("iconPath")):
25
27
  try:
26
28
  cli_dir = os.path.dirname(os.path.abspath(__file__))
27
29
  bundled_icon = os.path.join(cli_dir, "default_icon.png")
28
-
29
30
  if os.path.exists(bundled_icon):
30
31
  shutil.copy2(bundled_icon, default_icon_dest)
31
32
  save_local_config({"iconPath": default_icon_dest})
32
33
  print_info("Installed default AppForge app icon.")
33
- except Exception as e:
34
+ except Exception:
34
35
  pass
35
36
 
37
+ # 2. Get the platform that was saved during 'create' (default to android if new)
38
+ saved_platform = current_config.get("platform", "android")
36
39
  save_local_config({"category": category, "project_type": proj_type})
37
40
 
38
41
  if category == "web":
39
- web_dir = prompt("Confirm web build directory", project_info["webDir"])
40
- platform_choice = prompt("Select platforms (android, ios, both)", "android").lower()
41
- if platform_choice == "both" or platform_choice == "all":
42
+ web_dir = prompt("Confirm web build directory", current_config.get("webDir", project_info["webDir"]))
43
+ # --- FIXED: Uses saved_platform as default ---
44
+ platform_choice = prompt("Select platforms (android, ios, both)", saved_platform).lower()
45
+ if platform_choice in ["both", "all"]:
42
46
  platforms = ["android", "ios"]
43
47
  platform_choice = "android,ios"
44
48
  elif "ios" in platform_choice:
@@ -54,43 +58,36 @@ def init_project():
54
58
  print_info(f"Native {proj_type} project detected. Bypassing Capacitor setup.")
55
59
 
56
60
  if proj_type == "flutter":
57
- platform_choice = prompt("Select platforms (e.g., android, ios, windows, linux, or 'all')", "android").lower()
58
- if platform_choice == "all" or platform_choice == "both":
61
+ # --- FIXED: Uses saved_platform as default ---
62
+ platform_choice = prompt("Select platforms (e.g., android, ios, windows, linux, or 'all')", saved_platform).lower()
63
+ if platform_choice in ["all", "both"]:
59
64
  platform_choice = "android,ios,windows,linux"
60
65
  else:
61
- platform_choice = prompt("Select platform to build in cloud (android)", "android").lower()
66
+ # Standard Kotlin/Java is Android only
62
67
  platform_choice = "android"
63
68
 
64
69
  save_local_config({"platform": platform_choice})
65
70
 
71
+ # AI Scanning logic...
66
72
  print("")
67
73
  detected_features = scan_codebase_for_permissions()
68
-
69
74
  if detected_features:
70
75
  print_success(f"AI detected {len(detected_features)} native features required by your app:")
71
-
72
76
  for data in detected_features.values():
73
77
  print(f" {CYAN}●{RESET} {data['feature']} {GRAY}({data['desc']}){RESET}")
74
-
75
- print_info("The AppForge Cloud Server will automatically inject these permissions during the build process.")
78
+ print_info("The AppForge Cloud Server will automatically inject these permissions.")
76
79
  else:
77
80
  print_info("AI scan complete. No special native permissions detected.")
78
81
 
82
+ # Monorepo logic...
79
83
  try:
80
84
  import subprocess
81
- git_root = subprocess.check_output(
82
- ['git', 'rev-parse', '--show-toplevel'],
83
- text=True,
84
- stderr=subprocess.DEVNULL
85
- ).strip()
86
-
87
- sub_path = os.path.relpath(os.getcwd(), git_root)
88
-
85
+ git_root = subprocess.check_output(['git', 'rev-parse', '--show-toplevel'], text=True, stderr=subprocess.DEVNULL).strip()
86
+ sub_path = os.relpath(os.getcwd(), git_root)
89
87
  if sub_path and sub_path != '.':
90
88
  print_info(f"Detected sub-project path: {GRAY}{sub_path}{RESET}")
91
89
  save_local_config({"sub_path": sub_path})
92
-
93
- except (subprocess.CalledProcessError, FileNotFoundError):
90
+ except Exception:
94
91
  save_local_config({"sub_path": "."})
95
92
 
96
93
  print_success("Ready to send to the AppForge Cloud Builder.")
@@ -102,29 +99,31 @@ def configure_project():
102
99
  package_id = prompt("Package ID", config.get("packageId", "com.example.app"))
103
100
  build_mode = prompt("Default Build Mode (debug/release)", config.get("buildMode", "debug")).lower()
104
101
  if build_mode not in ['debug', 'release']: build_mode = 'debug'
102
+
105
103
  current_icon = config.get("iconPath", "")
106
104
  icon_prompt = prompt("Icon path (local PNG)", current_icon)
107
-
108
- # If they typed a new path, use it. Otherwise, keep the old one.
109
105
  final_icon_path = icon_prompt if icon_prompt else current_icon
110
106
 
111
107
  splash_img = prompt("Splash image (local PNG)", config.get("splashImg", ""))
112
108
  splash_bg = prompt("Splash background color (HEX)", config.get("splashBg", "#ffffff"))
113
109
 
110
+ # FIXED: Removed duplicate iconPath key
114
111
  new_config = {
115
112
  "appName": app_name,
116
113
  "packageId": package_id,
117
114
  "buildMode": build_mode,
118
115
  "iconPath": final_icon_path,
119
- "iconPath": icon_path,
120
116
  "splashImg": splash_img,
121
117
  "splashBg": splash_bg
122
118
  }
123
119
  save_local_config(new_config)
124
120
 
125
- web_dir = config.get("webDir", "dist")
121
+ # FIXED: Only run apply_configuration once, and ONLY for web projects
126
122
  if config.get("category") == "web":
123
+ web_dir = config.get("webDir", "dist")
127
124
  apply_configuration(app_name, package_id, final_icon_path, splash_img, splash_bg, web_dir)
125
+
126
+ print_success("Configuration updated successfully.")
128
127
  return new_config
129
128
 
130
129
  def display_app_details(config):
@@ -330,7 +329,7 @@ def show_argu():
330
329
  print(" appforge init - Initialize an AppForge project")
331
330
  print(" appforge configure - Open interactive settings")
332
331
  print(f" {CYAN}info{RESET} - View CLI version and release notes")
333
- print(f" {CYAN}create <framework> <name>{RESET} Scaffold a new app (vite, flutter, html, kotlin, win, linux), {BLUE}Ex - appforge create flutter myapp{RESET}")
332
+ print(f" {CYAN}create <framework> <name>{RESET} Scaffold a new app (vite, flutter, html, kotlin), {BLUE}Ex - appforge create flutter myapp{RESET}")
334
333
  print(" appforge build - Package project and send to build repo")
335
334
  print(" appforge status - Check build status")
336
335
  print(" appforge download - Download the built APK")
@@ -0,0 +1,165 @@
1
+ import os
2
+ import shutil
3
+ import subprocess
4
+ import re
5
+ import time
6
+ from .utils import print_success, print_info, print_error, prompt, print_progress_bar, run_with_spinner, CYAN, RESET, BOLD
7
+ from .config import save_local_config
8
+
9
+ TEMPLATES = {
10
+ "vite": {
11
+ "cmd": "npm create vite@latest {name} -- --template react-ts",
12
+ "desc": "React + TypeScript + Vite (Fast Web App)",
13
+ "type": "vite",
14
+ "category": "web"
15
+ },
16
+ "flutter": {
17
+ "cmd": "git clone --progress --depth 1 https://github.com/flutter/samples.git _temp_samples",
18
+ "desc": "Official Flutter Starter App (Native)",
19
+ "type": "flutter",
20
+ "category": "native"
21
+ },
22
+ "kotlin": {
23
+ "cmd": "git clone --progress --depth 1 https://github.com/android/architecture-samples.git _temp_kt",
24
+ "desc": "Native Android Kotlin (Jetpack Compose)",
25
+ "type": "android_native",
26
+ "category": "native"
27
+ },
28
+ "html": {
29
+ "cmd": None,
30
+ "desc": "Plain HTML/CSS/JS (Simple Web App)",
31
+ "type": "html",
32
+ "category": "web"
33
+ }
34
+ }
35
+
36
+ def generate_html_template(project_name):
37
+ os.makedirs(project_name, exist_ok=True)
38
+ os.makedirs(os.path.join(project_name, "www"), exist_ok=True)
39
+
40
+ html_content = f"""<!DOCTYPE html>
41
+ <html lang="en">
42
+ <head>
43
+ <meta charset="UTF-8">
44
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
45
+ <title>{project_name}</title>
46
+ <style>
47
+ body {{ font-family: system-ui, -apple-system, sans-serif; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #0f172a; color: white; }}
48
+ .card {{ background: rgba(255,255,255,0.1); padding: 2rem; border-radius: 16px; text-align: center; border: 1px solid rgba(255,255,255,0.2); backdrop-filter: blur(10px); }}
49
+ h1 {{ margin-top: 0; color: #6366f1; }}
50
+ </style>
51
+ </head>
52
+ <body>
53
+ <div class="card">
54
+ <h1>Welcome to {project_name}</h1>
55
+ <p>Built with ▲ AppForge</p>
56
+ </div>
57
+ </body>
58
+ </html>"""
59
+
60
+ with open(os.path.join(project_name, "www", "index.html"), "w") as f:
61
+ f.write(html_content)
62
+
63
+ def create_project(framework, project_name):
64
+ if framework not in TEMPLATES:
65
+ print_error(f"Unknown framework: {framework}.")
66
+
67
+ if os.path.exists(project_name):
68
+ print_error(f"Directory '{project_name}' already exists.")
69
+
70
+ template = TEMPLATES[framework]
71
+ print_info(f"Creating a new {BOLD}{template['desc']}{RESET} project in {CYAN}./{project_name}{RESET}...")
72
+
73
+ # 1. Platform selection
74
+ print("")
75
+ if framework == "flutter":
76
+ p_choice = prompt("Select platforms (android, ios, windows, linux, or 'all')", "android").lower()
77
+ if p_choice in ["all", "both"]: p_choice = "android,ios,windows,linux"
78
+ elif template["category"] == "web":
79
+ p_choice = prompt("Select platforms (android, ios, both)", "android").lower()
80
+ if p_choice in ["all", "both"]: p_choice = "android,ios"
81
+ else:
82
+ p_choice = "android"
83
+ print("")
84
+
85
+ # 2. Download with Smoothing
86
+ if framework == "html":
87
+ generate_html_template(project_name)
88
+ else:
89
+ cmd = template["cmd"].format(name=project_name)
90
+ try:
91
+ # We use non-blocking IO to allow the bar to animate even between data chunks
92
+ process = subprocess.Popen(
93
+ cmd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.DEVNULL, text=True, bufsize=0
94
+ )
95
+ os.set_blocking(process.stderr.fileno(), False)
96
+
97
+ progress_regex = re.compile(r"(\d+)%")
98
+ real_p = 0.0
99
+ display_p = 0.0
100
+
101
+ # --- SMOOTHING LOOP ---
102
+ while process.poll() is None or display_p < real_p:
103
+ try:
104
+ # Read all available data from stderr
105
+ data = process.stderr.read()
106
+ if data:
107
+ matches = progress_regex.findall(data)
108
+ if matches:
109
+ real_p = float(matches[-1])
110
+ except (TypeError, IOError):
111
+ pass # No data available right now
112
+
113
+ # If real_p hasn't updated yet, keep display_p moving slowly (fake inertia)
114
+ if display_p < real_p:
115
+ # Catch up 10% of the remaining distance per frame
116
+ step = (real_p - display_p) * 0.15
117
+ display_p += max(step, 0.5)
118
+ elif display_p < 99.0 and process.poll() is None:
119
+ # Creep forward slowly while waiting for more data
120
+ display_p += 0.1
121
+
122
+ if display_p > 100: display_p = 100.0
123
+
124
+ print_progress_bar(display_p, f"Downloading {framework} template")
125
+ time.sleep(0.05) # ~20 FPS for smoothness
126
+
127
+ print_progress_bar(100.0, f"Downloading {framework} template")
128
+
129
+ # Step 2: Extracting
130
+ def do_extract():
131
+ if framework == "flutter":
132
+ shutil.move("_temp_samples/provider_counter", project_name)
133
+ elif framework == "kotlin":
134
+ shutil.move("_temp_kt", project_name)
135
+ run_with_spinner(do_extract, "Extracting starter project")
136
+
137
+ # Step 3: Cleaning
138
+ def do_cleanup():
139
+ if os.path.exists("_temp_samples"): shutil.rmtree("_temp_samples", ignore_errors=True)
140
+ if os.path.exists("_temp_kt"): shutil.rmtree("_temp_kt", ignore_errors=True)
141
+ git_dir = os.path.join(project_name, ".git")
142
+ if os.path.exists(git_dir): shutil.rmtree(git_dir, ignore_errors=True)
143
+ run_with_spinner(do_cleanup, "Optimizing project size")
144
+
145
+ except Exception as e:
146
+ print_error(f"Creation failed: {e}")
147
+
148
+ # Step 4: Auto-Initialization
149
+ def do_init():
150
+ orig = os.getcwd()
151
+ os.chdir(project_name)
152
+ save_local_config({
153
+ "category": template["category"],
154
+ "project_type": template["type"],
155
+ "platform": p_choice,
156
+ "appName": project_name,
157
+ "packageId": f"com.appforge.{project_name.lower().replace('-', '_')}",
158
+ "version": "1.0.0"
159
+ })
160
+ os.chdir(orig)
161
+
162
+ run_with_spinner(do_init, "Generating AppForge config")
163
+
164
+ print_success(f"\nProject '{project_name}' is ready!")
165
+ print(f"Next steps: {CYAN}cd {project_name}{RESET} -> {CYAN}appforge init{RESET}\n")
@@ -14,7 +14,7 @@ RED = "\033[31m"
14
14
  GRAY = "\033[90m"
15
15
  BLUE = "\033[34m"
16
16
 
17
- CLI_VERSION = "1.6.2"
17
+ CLI_VERSION = "1.6.4"
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.2
3
+ Version: 1.6.4
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.2"
7
+ version = "1.6.4"
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.2",
5
+ version="1.6.4",
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,106 +0,0 @@
1
- import os
2
- import shutil
3
- import subprocess
4
- import re
5
- from .utils import print_success, print_info, print_error, prompt, print_progress_bar, CYAN, RESET, BOLD
6
-
7
- TEMPLATES = {
8
- "vite": {
9
- "cmd": "npm create vite@latest {name} -- --template react-ts",
10
- "desc": "React + TypeScript + Vite (Fast Web App)"
11
- },
12
- "flutter": {
13
- "cmd": "git clone --progress https://github.com/flutter/samples.git _temp_samples && mv _temp_samples/provider_counter {name} && rm -rf _temp_samples",
14
- "desc": "Official Flutter Starter App (Native)"
15
- },
16
- "kotlin": {
17
- "cmd": "git clone --progress https://github.com/android/architecture-samples.git _temp_kt && mv _temp_kt {name} && rm -rf _temp_kt",
18
- "desc": "Native Android Kotlin (Jetpack Compose)"
19
- },
20
- "html": {
21
- "cmd": None,
22
- "desc": "Plain HTML/CSS/JS (Simple Web App)"
23
- },
24
- "win": {
25
- "cmd": "git clone --progress https://github.com/flutter/samples.git _temp_samples",
26
- "desc": "Flutter Windows Desktop App (.exe)",
27
- "base_framework": "flutter" # It uses Flutter under the hood!
28
- },
29
- "linux": {
30
- "cmd": "git clone --progress https://github.com/flutter/samples.git _temp_samples",
31
- "desc": "Flutter Linux Desktop App",
32
- "base_framework": "flutter" # It uses Flutter under the hood!
33
- }
34
- }
35
-
36
- def generate_html_template(project_name):
37
- # This function stays exactly the same
38
- os.makedirs(project_name, exist_ok=True)
39
- os.makedirs(os.path.join(project_name, "www"), exist_ok=True)
40
- html_content = f"""...""" # Keep your HTML content
41
- with open(os.path.join(project_name, "www", "index.html"), "w") as f:
42
- f.write(html_content)
43
-
44
- def create_project(framework, project_name):
45
- """Generates a new project with a real progress bar for git clone."""
46
- if framework not in TEMPLATES:
47
- print_error(f"Unknown framework: {framework}.")
48
-
49
- if os.path.exists(project_name):
50
- print_error(f"Directory '{project_name}' already exists.")
51
-
52
- template = TEMPLATES[framework]
53
- print_info(f"Creating a new {BOLD}{template['desc']}{RESET} project in {CYAN}./{project_name}{RESET}...")
54
-
55
- if framework == "html":
56
- generate_html_template(project_name)
57
- else:
58
- # --- NEW REAL-TIME PROGRESS BAR LOGIC ---
59
- cmd = template["cmd"].format(name=project_name)
60
-
61
- try:
62
- # We open a subprocess and read its stderr line by line
63
- process = subprocess.Popen(
64
- cmd,
65
- shell=True,
66
- stderr=subprocess.PIPE,
67
- stdout=subprocess.DEVNULL, # Hide normal git output
68
- text=True,
69
- bufsize=1 # Line-buffered
70
- )
71
-
72
- # Regex to find the percentage in git's output
73
- # It looks for "Receiving objects: 25%"
74
- progress_regex = re.compile(r"(\d+)%")
75
-
76
- # Read each line of output from the running git command
77
- for line in iter(process.stderr.readline, ''):
78
- match = progress_regex.search(line)
79
- if match:
80
- percentage = float(match.group(1))
81
- print_progress_bar(percentage, f"Downloading {framework} template")
82
-
83
- process.wait() # Wait for the command to finish
84
- if process.returncode != 0:
85
- raise Exception("Git clone failed.")
86
-
87
- print_progress_bar(100.0, f"Downloading {framework} template")
88
-
89
- # Post-clone cleanup for Flutter
90
- if framework == "flutter":
91
- shutil.move("_temp_samples/provider_counter", project_name)
92
- shutil.rmtree("_temp_samples", ignore_errors=True)
93
- shutil.rmtree(os.path.join(project_name, ".git"), ignore_errors=True)
94
-
95
- except Exception as e:
96
- # Clean up partial downloads on failure
97
- if os.path.exists("_temp_samples"):
98
- shutil.rmtree("_temp_samples", ignore_errors=True)
99
- print_error(f"Project creation failed: {e}")
100
- # --- END OF NEW LOGIC ---
101
-
102
- print_success(f"Project '{project_name}' created successfully!")
103
- print("\nNext steps:")
104
- print(f" {CYAN}cd {project_name}{RESET}")
105
- print(f" {CYAN}appforge init{RESET}")
106
- print("")
File without changes