Skip to content

Commit bee5999

Browse files
authored
Merge pull request #601 from AutomationSolutionz/auto-install-deps
Auto install nodejs and appium dependencies on every platform
2 parents 0709c47 + 2694e59 commit bee5999

File tree

5 files changed

+336
-18
lines changed

5 files changed

+336
-18
lines changed

.github/workflows/node_runner.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,25 @@ jobs:
2020
- name: Install MinGW-w64
2121
run: sudo apt-get update && sudo apt-get install -y mingw-w64
2222

23+
- name: Extract version
24+
id: version
25+
run: |
26+
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
27+
echo "version=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
28+
else
29+
echo "version=dev-${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
30+
fi
31+
32+
- name: Update Version.txt
33+
run: |
34+
sed -i "s/version = .*/version = ${{ steps.version.outputs.version }}/" Framework/Version.txt
35+
sed -i "s/date = .*/date = $(date +'%b %d, %Y')/" Framework/Version.txt
36+
2337
- name: Build
2438
working-directory: ./Apps/node_runner
2539
run: make all
40+
env:
41+
VERSION: ${{ steps.version.outputs.version }}
2642

2743
- name: Upload Build Artifacts
2844
uses: actions/upload-artifact@v4

Apps/node_runner/Makefile

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,23 @@
44
BUILD_DIR=build
55

66
# Version info
7-
VERSION=1.0.0
7+
VERSION?=dev
88
APPNAME=ZeuZ_Node
9+
LDFLAGS=-X main.version=$(VERSION)
910

1011
windows:
1112
x86_64-w64-mingw32-windres -o resource.syso resource.rc
12-
GOOS=windows GOARCH=amd64 go build -o '$(BUILD_DIR)/$(APPNAME).exe' main.go
13-
GOOS=windows GOARCH=arm64 go build -o '$(BUILD_DIR)/$(APPNAME)_arm64.exe' main.go
13+
GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o '$(BUILD_DIR)/$(APPNAME).exe' main.go
14+
GOOS=windows GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o '$(BUILD_DIR)/$(APPNAME)_arm64.exe' main.go
1415
rm -f resource.syso
1516

1617
mac:
17-
GOOS=darwin GOARCH=arm64 go build -o '$(BUILD_DIR)/$(APPNAME)_macos' main.go
18-
GOOS=darwin GOARCH=amd64 go build -o '$(BUILD_DIR)/$(APPNAME)_macos_amd64' main.go
18+
GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o '$(BUILD_DIR)/$(APPNAME)_macos' main.go
19+
GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o '$(BUILD_DIR)/$(APPNAME)_macos_amd64' main.go
1920

2021
linux:
21-
GOOS=linux GOARCH=amd64 go build -o '$(BUILD_DIR)/$(APPNAME)_linux' main.go
22-
GOOS=linux GOARCH=arm64 go build -o '$(BUILD_DIR)/$(APPNAME)_linux_arm64' main.go
22+
GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o '$(BUILD_DIR)/$(APPNAME)_linux' main.go
23+
GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o '$(BUILD_DIR)/$(APPNAME)_linux_arm64' main.go
2324

2425
checksums:
2526
cd $(BUILD_DIR) && sha256sum * > checksums.txt

Apps/node_runner/main.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"archive/zip"
5+
"flag"
56
"fmt"
67
"io"
78
"net/http"
@@ -15,10 +16,14 @@ import (
1516
)
1617

1718
const (
18-
zeuzURL = "https://github.com/AutomationSolutionz/Zeuz_Python_Node/archive/refs/heads/dev.zip"
1919
zeuzDir = "ZeuZ_Node"
2020
)
2121

22+
var (
23+
version = "dev"
24+
branch = flag.String("branch", "", "Branch to download (defaults to tagged version)")
25+
)
26+
2227
// downloadFile downloads a file from URL to a local path
2328
func downloadFile(url, destPath string) error {
2429
resp, err := http.Get(url)
@@ -119,6 +124,17 @@ func unzip(zipFile, dest string) error {
119124
return nil
120125
}
121126

127+
// getZeuZNodeURL returns the appropriate download URL based on version and branch
128+
func getZeuZNodeURL() string {
129+
if *branch != "" {
130+
return fmt.Sprintf("https://github.com/AutomationSolutionz/Zeuz_Python_Node/archive/refs/heads/%s.zip", *branch)
131+
}
132+
if version != "dev" && !strings.HasPrefix(version, "dev-") {
133+
return fmt.Sprintf("https://github.com/AutomationSolutionz/Zeuz_Python_Node/archive/refs/tags/%s.zip", version)
134+
}
135+
return "https://github.com/AutomationSolutionz/Zeuz_Python_Node/archive/refs/heads/dev.zip"
136+
}
137+
122138
// setupZeuzNode downloads and extracts the ZeuZ Node repository if not already present
123139
func setupZeuzNode() error {
124140
// Check if ZeuZ Node directory already exists and contains files
@@ -145,7 +161,8 @@ func setupZeuzNode() error {
145161

146162
// Download zip file
147163
zipPath := filepath.Join(tempDir, "zeuz.zip")
148-
fmt.Println("Downloading ZeuZ Node repository...")
164+
zeuzURL := getZeuZNodeURL()
165+
fmt.Printf("Downloading ZeuZ Node repository from: %s\n", zeuzURL)
149166
if err := downloadFile(zeuzURL, zipPath); err != nil {
150167
return err
151168
}
@@ -244,6 +261,10 @@ func runUVCommands(args []string) error {
244261
}
245262

246263
func main() {
264+
flag.Parse()
265+
266+
fmt.Printf("ZeuZ Node Runner v%s\n", version)
267+
247268
// Setup ZeuZ Node directory and change into it
248269
if err := setupZeuzNode(); err != nil {
249270
fmt.Printf("Error setting up ZeuZ Node: %v\n", err)
@@ -272,8 +293,8 @@ func main() {
272293
fmt.Printf("Error updating path: %v\n", err)
273294
}
274295

275-
// Get command line arguments, excluding the program name
276-
args := os.Args[1:]
296+
// Get remaining command line arguments after flag parsing
297+
args := flag.Args()
277298

278299
// Run UV commands with arguments
279300
if err := runUVCommands(args); err != nil {
Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
#!/usr/bin/env python3
2+
import os
3+
import platform
4+
import subprocess
5+
import tarfile
6+
import zipfile
7+
from pathlib import Path
8+
from urllib.request import urlretrieve
9+
import json
10+
11+
# This should always be the latest LTS version
12+
NODE_VERSION = "22.20.0"
13+
14+
15+
def get_node_url():
16+
"""Get Node.js download URL based on OS and architecture."""
17+
system = platform.system().lower()
18+
arch = platform.machine().lower()
19+
20+
if system == "darwin":
21+
os_name = "darwin"
22+
ext = "tar.gz"
23+
elif system == "linux":
24+
os_name = "linux"
25+
ext = "tar.xz"
26+
elif system == "windows":
27+
os_name = "win"
28+
ext = "zip"
29+
else:
30+
raise Exception(f"Unsupported OS: {system}")
31+
32+
if arch in ["x86_64", "amd64"]:
33+
arch = "x64"
34+
elif arch in ["arm64", "aarch64"]:
35+
arch = "arm64"
36+
else:
37+
raise Exception(f"Unsupported architecture: {arch}")
38+
39+
return f"https://nodejs.org/dist/v{NODE_VERSION}/node-v{NODE_VERSION}-{os_name}-{arch}.{ext}"
40+
41+
42+
def get_node_dir():
43+
"""Get Node.js installation directory."""
44+
node_dir = Path.home() / ".zeuz" / "nodejs"
45+
node_dir.mkdir(parents=True, exist_ok=True)
46+
return node_dir
47+
48+
49+
def extract_archive(archive_path, dest_dir):
50+
"""Extract tar.gz, tar.xz, or zip archive."""
51+
if archive_path.suffix == ".zip":
52+
with zipfile.ZipFile(archive_path, "r") as zip_ref:
53+
# Get root directory name
54+
root_dir = zip_ref.namelist()[0].split("/")[0]
55+
for member in zip_ref.namelist():
56+
if member.startswith(root_dir + "/"):
57+
# Remove root directory from path
58+
target_path = dest_dir / Path(member).relative_to(root_dir)
59+
if member.endswith("/"):
60+
target_path.mkdir(parents=True, exist_ok=True)
61+
else:
62+
target_path.parent.mkdir(parents=True, exist_ok=True)
63+
with (
64+
zip_ref.open(member) as source,
65+
open(target_path, "wb") as target,
66+
):
67+
target.write(source.read())
68+
else:
69+
with tarfile.open(archive_path, "r:*") as tar:
70+
# Get root directory name
71+
root_dir = tar.getnames()[0].split("/")[0]
72+
for member in tar.getmembers():
73+
if member.name.startswith(root_dir + "/"):
74+
# Remove root directory from path
75+
member.name = str(Path(member.name).relative_to(root_dir))
76+
tar.extract(member, dest_dir)
77+
78+
79+
def install_nodejs():
80+
"""Download and install Node.js locally."""
81+
node_dir = get_node_dir()
82+
83+
# Check if already installed
84+
node_bin = node_dir / ("node.exe" if platform.system() == "Windows" else "bin/node")
85+
if node_bin.exists():
86+
print("Node.js already installed")
87+
return
88+
89+
print(f"Installing Node.js v{NODE_VERSION}...")
90+
91+
# Create installation directory
92+
node_dir.mkdir(parents=True, exist_ok=True)
93+
94+
# Download Node.js
95+
url = get_node_url()
96+
archive_name = Path(url).name
97+
archive_path = node_dir / archive_name
98+
99+
print("Downloading Node.js...")
100+
urlretrieve(url, archive_path)
101+
102+
try:
103+
# Extract Node.js
104+
print("Extracting Node.js...")
105+
extract_archive(archive_path, node_dir)
106+
print("Node.js installation completed")
107+
finally:
108+
# Clean up
109+
if archive_path.exists():
110+
archive_path.unlink()
111+
112+
113+
def get_npm_path():
114+
"""Get npm binary path."""
115+
node_dir = get_node_dir()
116+
if platform.system() == "Windows":
117+
return node_dir / "npm.cmd"
118+
else:
119+
return node_dir / "bin" / "npm"
120+
121+
122+
def get_appium_path():
123+
"""Get appium binary path."""
124+
node_dir = get_node_dir()
125+
if platform.system() == "Windows":
126+
return node_dir / "appium.cmd"
127+
else:
128+
return node_dir / "bin" / "appium"
129+
130+
131+
def install_drivers(drivers):
132+
"""Install specified Appium drivers."""
133+
appium_path = get_appium_path()
134+
135+
for driver in drivers:
136+
try:
137+
subprocess.run([str(appium_path), "driver", "install", driver], check=True)
138+
print(f"Successfully installed {driver} driver")
139+
except subprocess.CalledProcessError:
140+
print(f"Warning: Failed to install {driver} driver, continuing...")
141+
142+
143+
def install_appium():
144+
"""Install Appium and drivers using local Node.js."""
145+
npm_path = get_npm_path()
146+
147+
if not npm_path.exists():
148+
raise Exception("npm not found. Install Node.js first.")
149+
150+
print("Installing Appium...")
151+
subprocess.run([str(npm_path), "install", "-g", "appium"], check=True)
152+
153+
print("Installing Appium drivers...")
154+
install_drivers(get_required_drivers())
155+
print("Appium installation completed")
156+
157+
158+
def update_path():
159+
"""Add Node.js binaries to PATH."""
160+
node_dir = get_node_dir()
161+
if platform.system() == "Windows":
162+
bin_dir = str(node_dir)
163+
else:
164+
bin_dir = str(node_dir / "bin")
165+
166+
current_path = os.environ.get("PATH", "")
167+
if bin_dir not in current_path:
168+
os.environ["PATH"] = f"{bin_dir}{os.pathsep}{current_path}"
169+
170+
171+
def get_required_drivers():
172+
"""Get list of required drivers for current platform."""
173+
system = platform.system().lower()
174+
if system == "windows":
175+
return ["uiautomator2", "windows"]
176+
elif system == "darwin":
177+
return ["uiautomator2", "xcuitest", "mac2"]
178+
elif system == "linux":
179+
return ["uiautomator2"]
180+
return []
181+
182+
183+
def check_appium_drivers():
184+
"""Check if required Appium drivers are installed."""
185+
appium_path = get_appium_path()
186+
if not appium_path.exists():
187+
return []
188+
189+
try:
190+
result = subprocess.run(
191+
[str(appium_path), "driver", "list", "--json"],
192+
capture_output=True,
193+
text=True,
194+
)
195+
drivers_data = json.loads(result.stdout)
196+
return [name for name, info in drivers_data.items() if info.get("installed", False)]
197+
except: # noqa: E722
198+
return []
199+
200+
201+
def check_installations():
202+
"""Check if Node.js, Appium and required drivers are installed."""
203+
node_dir = get_node_dir()
204+
node_bin = node_dir / ("node.exe" if platform.system() == "Windows" else "bin/node")
205+
206+
# Check for Appium in global npm modules
207+
npm_path = get_npm_path()
208+
appium_installed = False
209+
210+
if npm_path.exists():
211+
try:
212+
result = subprocess.run(
213+
[str(npm_path), "list", "-g", "--json", "appium"], capture_output=True, text=True
214+
)
215+
npm_data = json.loads(result.stdout)
216+
appium_installed = "appium" in npm_data.get("dependencies", {})
217+
except: # noqa: E722
218+
pass
219+
220+
# Check drivers
221+
required_drivers = get_required_drivers()
222+
installed_drivers = check_appium_drivers() if appium_installed else []
223+
missing_drivers = [d for d in required_drivers if d not in installed_drivers]
224+
225+
return node_bin.exists(), appium_installed, missing_drivers
226+
227+
228+
def install_missing_drivers(missing_drivers):
229+
"""Install missing Appium drivers."""
230+
print("Installing missing Appium drivers...")
231+
install_drivers(missing_drivers)
232+
233+
234+
def setup_nodejs_appium():
235+
"""Main setup function."""
236+
try:
237+
update_path() # Ensure Node.js is in PATH from the start
238+
239+
print("Checking Node.js and Appium installation...")
240+
node_installed, appium_installed, missing_drivers = check_installations()
241+
242+
if not node_installed:
243+
install_nodejs()
244+
update_path() # Update PATH after installation
245+
else:
246+
print("Node.js already installed")
247+
248+
if not appium_installed:
249+
install_appium()
250+
elif missing_drivers:
251+
install_missing_drivers(missing_drivers)
252+
else:
253+
print("Appium and all required drivers already installed")
254+
255+
print("Node.js and Appium setup completed successfully")
256+
return True
257+
except Exception as e:
258+
print(f"Error during setup: {e}")
259+
return False
260+
261+
262+
if __name__ == "__main__":
263+
setup_nodejs_appium()

0 commit comments

Comments
 (0)