@ionyx-apps/cli 0.2.1 ā 0.3.1
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.
- package/bin/ionyx.exe +0 -0
- package/ionyx-cli/Cargo.toml +40 -0
- package/ionyx-cli/src/commands/build.rs +53 -0
- package/ionyx-cli/src/commands/bundle.rs +349 -0
- package/ionyx-cli/src/commands/config.rs +36 -0
- package/ionyx-cli/src/commands/dev.rs +142 -0
- package/ionyx-cli/src/commands/doctor.rs +158 -0
- package/ionyx-cli/src/commands/info.rs +79 -0
- package/ionyx-cli/src/commands/init.rs +64 -0
- package/ionyx-cli/src/commands/mod.rs +9 -0
- package/ionyx-cli/src/commands/plugin.rs +34 -0
- package/ionyx-cli/src/commands/run.rs +54 -0
- package/ionyx-cli/src/config/mod.rs +155 -0
- package/ionyx-cli/src/main.rs +161 -0
- package/ionyx-cli/src/templates/basic/src-ionyx/Cargo.toml +26 -20
- package/ionyx-cli/src/templates/basic/src-ionyx/src/lib.rs +6 -0
- package/ionyx-cli/src/templates/basic/src-ionyx/src/protocol.rs +5 -4
- package/ionyx-cli/src/templates/leptos/Cargo.toml +13 -13
- package/ionyx-cli/src/templates/leptos/src-ionyx/Cargo.toml +20 -20
- package/ionyx-cli/src/templates/leptos/src-ionyx/src/protocol.rs +5 -4
- package/ionyx-cli/src/templates/react/src-ionyx/Cargo.toml +26 -20
- package/ionyx-cli/src/templates/react/src-ionyx/src/lib.rs +6 -0
- package/ionyx-cli/src/templates/react/src-ionyx/src/protocol.rs +5 -4
- package/ionyx-cli/src/templates/src-ionyx/Cargo.toml +18 -18
- package/ionyx-cli/src/templates/svelte/src-ionyx/Cargo.toml +26 -20
- package/ionyx-cli/src/templates/svelte/src-ionyx/src/lib.rs +6 -0
- package/ionyx-cli/src/templates/svelte/src-ionyx/src/protocol.rs +5 -4
- package/ionyx-cli/src/templates/vanilla/src-ionyx/Cargo.toml +26 -20
- package/ionyx-cli/src/templates/vanilla/src-ionyx/src/lib.rs +6 -0
- package/ionyx-cli/src/templates/vanilla/src-ionyx/src/protocol.rs +5 -4
- package/ionyx-cli/src/templates/vue/src-ionyx/Cargo.toml +26 -20
- package/ionyx-cli/src/templates/vue/src-ionyx/src/lib.rs +6 -0
- package/ionyx-cli/src/templates/vue/src-ionyx/src/protocol.rs +5 -4
- package/package.json +12 -4
- package/bin/cargo-ionyx-win.exe +0 -0
- package/bin/cargo-ionyx.exe +0 -0
- package/ionyx-cli/src/templates/react/src-ionyx/Cargo.lock +0 -5536
package/bin/ionyx.exe
CHANGED
|
Binary file
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "ionyx-cli"
|
|
3
|
+
version = "0.3.1"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
description = "Ionyx Framework CLI Tool"
|
|
6
|
+
authors = ["Ionyx Team"]
|
|
7
|
+
license = "MIT"
|
|
8
|
+
repository = "https://github.com/ionyx-framework/ionyx-cli"
|
|
9
|
+
|
|
10
|
+
[[bin]]
|
|
11
|
+
name = "ionyx"
|
|
12
|
+
path = "src/main.rs"
|
|
13
|
+
|
|
14
|
+
[dependencies]
|
|
15
|
+
clap = { version = "4.4", features = ["derive"] }
|
|
16
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
17
|
+
serde_json = "1.0"
|
|
18
|
+
tokio = { version = "1.0", features = ["full"] }
|
|
19
|
+
anyhow = "1.0"
|
|
20
|
+
colored = "2.0"
|
|
21
|
+
dirs = "5.0"
|
|
22
|
+
handlebars = "4.5"
|
|
23
|
+
zip = "0.6"
|
|
24
|
+
reqwest = { version = "0.11", features = ["json"] }
|
|
25
|
+
futures = "0.3"
|
|
26
|
+
include_dir = "0.7"
|
|
27
|
+
dialoguer = "0.11"
|
|
28
|
+
os_info = "3.8"
|
|
29
|
+
toml = "0.8"
|
|
30
|
+
[workspace]
|
|
31
|
+
exclude = [
|
|
32
|
+
"src/templates/basic/src-ionyx",
|
|
33
|
+
"src/templates/leptos",
|
|
34
|
+
"src/templates/leptos/src-ionyx",
|
|
35
|
+
"src/templates/react/src-ionyx",
|
|
36
|
+
"src/templates/src-ionyx",
|
|
37
|
+
"src/templates/svelte/src-ionyx",
|
|
38
|
+
"src/templates/vanilla/src-ionyx",
|
|
39
|
+
"src/templates/vue/src-ionyx"
|
|
40
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use colored::*;
|
|
3
|
+
use tokio::process::Command;
|
|
4
|
+
use std::path::Path;
|
|
5
|
+
|
|
6
|
+
pub async fn execute(target: &str, minify: bool) -> Result<()> {
|
|
7
|
+
println!("šØ Building Ionyx project for target: {}", target.cyan());
|
|
8
|
+
|
|
9
|
+
if !Path::new("package.json").exists() {
|
|
10
|
+
return Err(anyhow::anyhow!("No package.json found. Please run this command in an Ionyx project directory."));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// 1. Build Frontend
|
|
14
|
+
println!("š Building frontend...");
|
|
15
|
+
let fe_status = if cfg!(target_os = "windows") {
|
|
16
|
+
Command::new("cmd")
|
|
17
|
+
.args(&["/C", "npm", "run", "build"])
|
|
18
|
+
.env("BUILD_TARGET", target)
|
|
19
|
+
.env("MINIFY", if minify { "true" } else { "false" })
|
|
20
|
+
.status()
|
|
21
|
+
.await?
|
|
22
|
+
} else {
|
|
23
|
+
Command::new("npm")
|
|
24
|
+
.args(&["run", "build"])
|
|
25
|
+
.env("BUILD_TARGET", target)
|
|
26
|
+
.env("MINIFY", if minify { "true" } else { "false" })
|
|
27
|
+
.status()
|
|
28
|
+
.await?
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
if !fe_status.success() {
|
|
32
|
+
return Err(anyhow::anyhow!("Frontend build failed"));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Build Backend
|
|
36
|
+
println!("š¦ Building Rust backend host...");
|
|
37
|
+
let mut be_cmd = Command::new("cargo");
|
|
38
|
+
be_cmd.args(&["build", "--release"]);
|
|
39
|
+
|
|
40
|
+
if Path::new("src-ionyx").exists() {
|
|
41
|
+
be_cmd.current_dir("src-ionyx");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
let be_status = be_cmd.status().await?;
|
|
45
|
+
if !be_status.success() {
|
|
46
|
+
return Err(anyhow::anyhow!("Backend build failed"));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
println!("\nā
Full build completed successfully!");
|
|
50
|
+
println!("š¦ Backend binary: src-ionyx/target/release/ionyx-host");
|
|
51
|
+
|
|
52
|
+
Ok(())
|
|
53
|
+
}
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use colored::*;
|
|
3
|
+
use std::env;
|
|
4
|
+
use std::fs;
|
|
5
|
+
use std::path::{Path, PathBuf};
|
|
6
|
+
use tokio::process::Command;
|
|
7
|
+
use std::io::{Read, Write};
|
|
8
|
+
use zip::write::FileOptions;
|
|
9
|
+
|
|
10
|
+
pub async fn execute(platform: Option<String>, format: Option<String>) -> Result<()> {
|
|
11
|
+
let target_platform = platform.unwrap_or_else(|| env::consts::OS.to_string());
|
|
12
|
+
let target_format = format.unwrap_or_else(|| {
|
|
13
|
+
if target_platform == "windows" {
|
|
14
|
+
"nsis".to_string()
|
|
15
|
+
} else if target_platform == "macos" {
|
|
16
|
+
"dmg".to_string()
|
|
17
|
+
} else {
|
|
18
|
+
"appimage".to_string()
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
println!("š¦ Bundling Ionyx project for {}: {}", target_platform.cyan(), target_format.green());
|
|
23
|
+
|
|
24
|
+
let (app_name, app_version) = if Path::new("package.json").exists() {
|
|
25
|
+
let content = fs::read_to_string("package.json")?;
|
|
26
|
+
let pkg: serde_json::Value = serde_json::from_str(&content)?;
|
|
27
|
+
let name = pkg["name"].as_str().unwrap_or("ionyx-app").to_string();
|
|
28
|
+
let version = pkg["version"].as_str().unwrap_or("1.0.0").to_string();
|
|
29
|
+
(name, version)
|
|
30
|
+
} else {
|
|
31
|
+
("ionyx-app".to_string(), "1.0.0".to_string())
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
let icon_path = if Path::new("ionyx.config.json").exists() {
|
|
35
|
+
let content = fs::read_to_string("ionyx.config.json")?;
|
|
36
|
+
let cfg: serde_json::Value = serde_json::from_str(&content)?;
|
|
37
|
+
cfg["window"]["icon"].as_str().map(|s| s.to_string())
|
|
38
|
+
} else {
|
|
39
|
+
None
|
|
40
|
+
};
|
|
41
|
+
let icon_path_str = icon_path.unwrap_or_else(|| "icon.png".to_string());
|
|
42
|
+
|
|
43
|
+
// 1. Build the project first
|
|
44
|
+
println!("šØ Running full build...");
|
|
45
|
+
crate::commands::build::execute("all", true).await?;
|
|
46
|
+
|
|
47
|
+
// 2. Prepare installer directory
|
|
48
|
+
let installer_dir = Path::new("installers");
|
|
49
|
+
if installer_dir.exists() {
|
|
50
|
+
fs::remove_dir_all(installer_dir)?;
|
|
51
|
+
}
|
|
52
|
+
fs::create_dir_all(installer_dir)?;
|
|
53
|
+
|
|
54
|
+
// 3. Find the executable
|
|
55
|
+
let mut exe_path = PathBuf::from("src-ionyx/target/release");
|
|
56
|
+
if target_platform == "windows" {
|
|
57
|
+
exe_path.push("ionyx-host.exe");
|
|
58
|
+
} else {
|
|
59
|
+
exe_path.push("ionyx-host");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if !exe_path.exists() {
|
|
63
|
+
return Err(anyhow::anyhow!("Binary not found at {:?}. Did the build fail?", exe_path));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// 4. Platform-specific bundling
|
|
67
|
+
match target_platform.as_str() {
|
|
68
|
+
"windows" => {
|
|
69
|
+
if target_format == "nsis" {
|
|
70
|
+
bundle_windows_nsis(installer_dir, &exe_path, &app_name, &app_version, &icon_path_str).await?;
|
|
71
|
+
} else if target_format == "wix" {
|
|
72
|
+
bundle_windows_wix(installer_dir, &exe_path, &app_name, &app_version, &icon_path_str).await?;
|
|
73
|
+
} else {
|
|
74
|
+
return Err(anyhow::anyhow!("Unsupported format for windows: {}", target_format));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
"macos" => {
|
|
78
|
+
bundle_macos_dmg(installer_dir, &exe_path, &app_name, &app_version, &icon_path_str).await?;
|
|
79
|
+
}
|
|
80
|
+
"linux" => {
|
|
81
|
+
bundle_linux_appimage(installer_dir, &exe_path, &app_name, &app_version, &icon_path_str).await?;
|
|
82
|
+
}
|
|
83
|
+
_ => return Err(anyhow::anyhow!("Unsupported platform: {}", target_platform)),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
println!("\nš Bundling completed successfully!");
|
|
87
|
+
println!("š Output directory: {}", "installers/".cyan());
|
|
88
|
+
|
|
89
|
+
Ok(())
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async fn bundle_windows_nsis(installer_dir: &Path, exe_path: &Path, app_name: &str, app_version: &str, icon_path: &str) -> Result<()> {
|
|
93
|
+
println!("šŖ Building Windows EXE installer via NSIS...");
|
|
94
|
+
|
|
95
|
+
let icon_absolute = if Path::new(icon_path).exists() {
|
|
96
|
+
Some(fs::canonicalize(icon_path)?)
|
|
97
|
+
} else {
|
|
98
|
+
None
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
let nsis_script = format!(
|
|
102
|
+
r#"!define APP_NAME "{}"
|
|
103
|
+
!define APP_VERSION "{}"
|
|
104
|
+
!define APP_PUBLISHER "Ionyx Framework Team"
|
|
105
|
+
!define APP_EXE "ionyx-host.exe"
|
|
106
|
+
{}
|
|
107
|
+
|
|
108
|
+
Name "${{APP_NAME}}"
|
|
109
|
+
OutFile "{}\IonyxApp-Setup.exe"
|
|
110
|
+
InstallDir "$PROGRAMFILES64\${{APP_NAME}}"
|
|
111
|
+
RequestExecutionLevel admin
|
|
112
|
+
|
|
113
|
+
Page directory
|
|
114
|
+
Page instfiles
|
|
115
|
+
|
|
116
|
+
Section "MainSection" SEC01
|
|
117
|
+
SetOutPath "$INSTDIR"
|
|
118
|
+
File "{}"
|
|
119
|
+
CreateShortCut "$DESKTOP\${{APP_NAME}}.lnk" "$INSTDIR\${{APP_EXE}}" "" "{}" 0
|
|
120
|
+
SectionEnd
|
|
121
|
+
"#,
|
|
122
|
+
app_name,
|
|
123
|
+
app_version,
|
|
124
|
+
if let Some(ref icon) = icon_absolute {
|
|
125
|
+
format!("!define APP_ICON \"{}\"", icon.display())
|
|
126
|
+
} else {
|
|
127
|
+
"".to_string()
|
|
128
|
+
},
|
|
129
|
+
installer_dir.display(),
|
|
130
|
+
exe_path.canonicalize()?.display(),
|
|
131
|
+
icon_absolute.map(|p| p.display().to_string()).unwrap_or_default()
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
let script_path = installer_dir.join("installer.nsi");
|
|
135
|
+
fs::write(&script_path, nsis_script)?;
|
|
136
|
+
|
|
137
|
+
let status = Command::new("makensis")
|
|
138
|
+
.arg(&script_path)
|
|
139
|
+
.status()
|
|
140
|
+
.await;
|
|
141
|
+
|
|
142
|
+
match status {
|
|
143
|
+
Ok(s) if s.success() => println!("ā
NSIS build successful"),
|
|
144
|
+
_ => {
|
|
145
|
+
println!("ā ļø NSIS (makensis) not found or failed. Falling back to structured ZIP.");
|
|
146
|
+
bundle_zip_fallback(installer_dir, exe_path, app_name, app_version).await?;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
Ok(())
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async fn bundle_windows_wix(installer_dir: &Path, exe_path: &Path, app_name: &str, _app_version: &str, _icon_path: &str) -> Result<()> {
|
|
154
|
+
println!("šŖ Building Windows MSI installer via WiX...");
|
|
155
|
+
// Placeholder for WiX implementation
|
|
156
|
+
Ok(())
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async fn bundle_macos_dmg(installer_dir: &Path, exe_path: &Path, app_name: &str, app_version: &str, icon_path: &str) -> Result<()> {
|
|
160
|
+
println!("š Building macOS DMG installer...");
|
|
161
|
+
|
|
162
|
+
let dmg_script = format!(
|
|
163
|
+
r#"#!/bin/bash
|
|
164
|
+
APP_NAME="{}"
|
|
165
|
+
TEMP_DIR="/tmp/${{APP_NAME}}"
|
|
166
|
+
APP_DIR="${{TEMP_DIR}}/${{APP_NAME}}.app"
|
|
167
|
+
|
|
168
|
+
mkdir -p "${{APP_DIR}}/Contents/MacOS"
|
|
169
|
+
mkdir -p "${{APP_DIR}}/Contents/Resources"
|
|
170
|
+
|
|
171
|
+
cp "{}" "${{APP_DIR}}/Contents/MacOS/ionyx-host"
|
|
172
|
+
{}
|
|
173
|
+
|
|
174
|
+
cat > "${{APP_DIR}}/Contents/Info.plist" << EOF
|
|
175
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
176
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
177
|
+
<plist version="1.0">
|
|
178
|
+
<dict>
|
|
179
|
+
<key>CFBundleExecutable</key>
|
|
180
|
+
<string>ionyx-host</string>
|
|
181
|
+
<key>CFBundleIdentifier</key>
|
|
182
|
+
<string>com.ionyx-framework.app</string>
|
|
183
|
+
<key>CFBundleName</key>
|
|
184
|
+
<string>${{APP_NAME}}</string>
|
|
185
|
+
<key>CFBundleVersion</key>
|
|
186
|
+
<string>1.0.0</string>
|
|
187
|
+
{}
|
|
188
|
+
</dict>
|
|
189
|
+
</plist>
|
|
190
|
+
EOF
|
|
191
|
+
|
|
192
|
+
hdiutil create -volname "${{APP_NAME}}" -srcfolder "${{TEMP_DIR}}" -ov -format UDZO "{}/{}.dmg"
|
|
193
|
+
rm -rf "${{TEMP_DIR}}"
|
|
194
|
+
"#,
|
|
195
|
+
app_name,
|
|
196
|
+
exe_path.canonicalize()?.display(),
|
|
197
|
+
if Path::new(icon_path).exists() {
|
|
198
|
+
format!("cp \"{}\" \"${{APP_DIR}}/Contents/Resources/icon.png\"", icon_path)
|
|
199
|
+
} else {
|
|
200
|
+
"".to_string()
|
|
201
|
+
},
|
|
202
|
+
if Path::new(icon_path).exists() {
|
|
203
|
+
"<key>CFBundleIconFile</key><string>icon.png</string>".to_string()
|
|
204
|
+
} else {
|
|
205
|
+
"".to_string()
|
|
206
|
+
},
|
|
207
|
+
installer_dir.display(),
|
|
208
|
+
app_name
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
let script_path = installer_dir.join("build-dmg.sh");
|
|
212
|
+
fs::write(&script_path, dmg_script)?;
|
|
213
|
+
|
|
214
|
+
let status = Command::new("bash")
|
|
215
|
+
.arg(&script_path)
|
|
216
|
+
.status()
|
|
217
|
+
.await;
|
|
218
|
+
|
|
219
|
+
match status {
|
|
220
|
+
Ok(s) if s.success() => println!("ā
DMG build successful"),
|
|
221
|
+
_ => {
|
|
222
|
+
println!("ā ļø macOS bundling tools not available or failed. Falling back to structured ZIP.");
|
|
223
|
+
bundle_zip_fallback(installer_dir, exe_path, app_name, app_version).await?;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
Ok(())
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async fn bundle_linux_appimage(installer_dir: &Path, exe_path: &Path, app_name: &str, app_version: &str, icon_path: &str) -> Result<()> {
|
|
231
|
+
println!("š§ Building Linux AppImage...");
|
|
232
|
+
|
|
233
|
+
let appimage_script = format!(
|
|
234
|
+
r#"#!/bin/bash
|
|
235
|
+
APP_NAME="{}"
|
|
236
|
+
APP_DIR="{}/${{APP_NAME}}.AppImage"
|
|
237
|
+
TEMP_DIR="{}/temp"
|
|
238
|
+
|
|
239
|
+
mkdir -p "${{TEMP_DIR}}/usr/bin"
|
|
240
|
+
mkdir -p "${{TEMP_DIR}}/usr/share/icons/hicolor/256x256/apps"
|
|
241
|
+
|
|
242
|
+
cp "{}" "${{TEMP_DIR}}/usr/bin/ionyx-host"
|
|
243
|
+
chmod +x "${{TEMP_DIR}}/usr/bin/ionyx-host"
|
|
244
|
+
|
|
245
|
+
{}
|
|
246
|
+
|
|
247
|
+
cat > "${{TEMP_DIR}}/usr/share/applications/${{APP_NAME}}.desktop" << EOF
|
|
248
|
+
[Desktop Entry]
|
|
249
|
+
Name=${{APP_NAME}}
|
|
250
|
+
Exec=ionyx-host
|
|
251
|
+
Icon=icon
|
|
252
|
+
Type=Application
|
|
253
|
+
Categories=Utility;
|
|
254
|
+
EOF
|
|
255
|
+
|
|
256
|
+
cat > "${{TEMP_DIR}}/AppRun" << EOF
|
|
257
|
+
#!/bin/bash
|
|
258
|
+
HERE="\$(dirname "\$0")"
|
|
259
|
+
exec "\$HERE/usr/bin/ionyx-host"
|
|
260
|
+
EOF
|
|
261
|
+
chmod +x "${{TEMP_DIR}}/AppRun"
|
|
262
|
+
|
|
263
|
+
# Fallback to simple tar if appimagetool not found
|
|
264
|
+
if command -v appimagetool >/dev/null; then
|
|
265
|
+
appimagetool "${{TEMP_DIR}}" "${{APP_DIR}}"
|
|
266
|
+
else
|
|
267
|
+
tar -czf "{}/{}.tar.gz" -C "{}" temp
|
|
268
|
+
fi
|
|
269
|
+
rm -rf "${{TEMP_DIR}}"
|
|
270
|
+
"#,
|
|
271
|
+
app_name,
|
|
272
|
+
installer_dir.display(),
|
|
273
|
+
installer_dir.display(),
|
|
274
|
+
exe_path.canonicalize()?.display(),
|
|
275
|
+
if Path::new(icon_path).exists() {
|
|
276
|
+
format!("cp \"{}\" \"${{TEMP_DIR}}/usr/share/icons/hicolor/256x256/apps/icon.png\"", icon_path)
|
|
277
|
+
} else {
|
|
278
|
+
"".to_string()
|
|
279
|
+
},
|
|
280
|
+
installer_dir.display(),
|
|
281
|
+
app_name,
|
|
282
|
+
installer_dir.display()
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
let script_path = installer_dir.join("build-appimage.sh");
|
|
286
|
+
fs::write(&script_path, appimage_script)?;
|
|
287
|
+
|
|
288
|
+
let status = Command::new("bash")
|
|
289
|
+
.arg(&script_path)
|
|
290
|
+
.status()
|
|
291
|
+
.await;
|
|
292
|
+
|
|
293
|
+
match status {
|
|
294
|
+
Ok(s) if s.success() => println!("ā
Linux bundling complete"),
|
|
295
|
+
_ => {
|
|
296
|
+
println!("ā ļø Linux bundling tools not available or failed. Falling back to structured ZIP.");
|
|
297
|
+
bundle_zip_fallback(installer_dir, exe_path, app_name, app_version).await?;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
Ok(())
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
async fn bundle_zip_fallback(installer_dir: &Path, exe_path: &Path, app_name: &str, app_version: &str) -> Result<()> {
|
|
305
|
+
println!("š¦ Creating a professional portable ZIP distribution...");
|
|
306
|
+
let zip_name = format!("{}-{}-portable.zip", app_name, app_version);
|
|
307
|
+
let zip_path = installer_dir.join(zip_name);
|
|
308
|
+
|
|
309
|
+
let file = fs::File::create(&zip_path)?;
|
|
310
|
+
let mut zip = zip::ZipWriter::new(file);
|
|
311
|
+
let options = FileOptions::default()
|
|
312
|
+
.compression_method(zip::CompressionMethod::Deflated)
|
|
313
|
+
.unix_permissions(0o755);
|
|
314
|
+
|
|
315
|
+
// 1. Add Main Executable
|
|
316
|
+
let exe_name = if cfg!(target_os = "windows") {
|
|
317
|
+
format!("{}.exe", app_name)
|
|
318
|
+
} else {
|
|
319
|
+
app_name.to_string()
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
zip.start_file(format!("{}/{}", app_name, exe_name), options)?;
|
|
323
|
+
let mut exe_file = fs::File::open(exe_path)?;
|
|
324
|
+
let mut buffer = Vec::new();
|
|
325
|
+
exe_file.read_to_end(&mut buffer)?;
|
|
326
|
+
zip.write_all(&buffer)?;
|
|
327
|
+
|
|
328
|
+
// 2. Add Icon if exists
|
|
329
|
+
if Path::new("icon.png").exists() {
|
|
330
|
+
zip.start_file(format!("{}/icon.png", app_name), options)?;
|
|
331
|
+
let mut icon_file = fs::File::open("icon.png")?;
|
|
332
|
+
let mut icon_buffer = Vec::new();
|
|
333
|
+
icon_file.read_to_end(&mut icon_buffer)?;
|
|
334
|
+
zip.write_all(&icon_buffer)?;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// 3. Add README.md
|
|
338
|
+
zip.start_file(format!("{}/README.txt", app_name), options)?;
|
|
339
|
+
let readme_content = format!(
|
|
340
|
+
"{} v{}\n\nPortable distribution created by Ionyx Framework š\n\nTo run the application, execute: {}\n",
|
|
341
|
+
app_name, app_version, exe_name
|
|
342
|
+
);
|
|
343
|
+
zip.write_all(readme_content.as_bytes())?;
|
|
344
|
+
|
|
345
|
+
zip.finish()?;
|
|
346
|
+
|
|
347
|
+
println!("ā
Structured portable ZIP created: {}", zip_path.display());
|
|
348
|
+
Ok(())
|
|
349
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use colored::*;
|
|
3
|
+
use crate::config::ProjectConfig;
|
|
4
|
+
use std::fs;
|
|
5
|
+
|
|
6
|
+
pub async fn execute(action: crate::ConfigAction) -> Result<()> {
|
|
7
|
+
match action {
|
|
8
|
+
crate::ConfigAction::Get { key } => {
|
|
9
|
+
let config = ProjectConfig::load()?;
|
|
10
|
+
if let Some(value) = config.get(&key) {
|
|
11
|
+
println!("{} = {}", key.cyan(), value.green());
|
|
12
|
+
} else {
|
|
13
|
+
println!("{} = {}", key.cyan(), "not found".red());
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
crate::ConfigAction::Set { key, value } => {
|
|
17
|
+
let mut config = ProjectConfig::load()?;
|
|
18
|
+
config.set(&key, &value);
|
|
19
|
+
config.save()?;
|
|
20
|
+
println!("ā
Set {} = {}", key.cyan(), value.green());
|
|
21
|
+
}
|
|
22
|
+
crate::ConfigAction::List => {
|
|
23
|
+
let config = ProjectConfig::load()?;
|
|
24
|
+
println!("š Configuration:");
|
|
25
|
+
for (key, value) in config.get_all() {
|
|
26
|
+
println!(" {} = {}", key.cyan(), value.green().to_string());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
crate::ConfigAction::Reset => {
|
|
30
|
+
ProjectConfig::reset()?;
|
|
31
|
+
println!("ā
Configuration reset to defaults");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
Ok(())
|
|
36
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
use anyhow::Result;
|
|
2
|
+
use colored::*;
|
|
3
|
+
use tokio::process::Command;
|
|
4
|
+
use std::path::Path;
|
|
5
|
+
use std::net::TcpStream;
|
|
6
|
+
use std::time::Duration;
|
|
7
|
+
use serde::Deserialize;
|
|
8
|
+
use tokio::fs;
|
|
9
|
+
|
|
10
|
+
#[derive(Deserialize)]
|
|
11
|
+
struct IonyxConfig {
|
|
12
|
+
build: Option<BuildConfig>,
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
#[derive(Deserialize)]
|
|
16
|
+
struct BuildConfig {
|
|
17
|
+
before_dev_command: Option<String>,
|
|
18
|
+
dev_url: Option<String>,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
#[derive(Deserialize)]
|
|
22
|
+
struct CargoToml {
|
|
23
|
+
package: PackageConfig,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
#[derive(Deserialize)]
|
|
27
|
+
struct PackageConfig {
|
|
28
|
+
name: String,
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
pub async fn execute(port: u16, _hot: bool) -> Result<()> {
|
|
32
|
+
println!("š„ Starting Ionyx development environment on port {}", port.to_string().cyan());
|
|
33
|
+
|
|
34
|
+
if !Path::new("package.json").exists() {
|
|
35
|
+
return Err(anyhow::anyhow!("No package.json found. Please run this command in an Ionyx project directory."));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Read ionyx.config.toml
|
|
39
|
+
let config_path = "src-ionyx/ionyx.config.toml";
|
|
40
|
+
if !Path::new(config_path).exists() {
|
|
41
|
+
return Err(anyhow::anyhow!("No {} found. Please ensure you are in an Ionyx project.", config_path));
|
|
42
|
+
}
|
|
43
|
+
let config_content = fs::read_to_string(config_path).await?;
|
|
44
|
+
let config: IonyxConfig = toml::from_str(&config_content)?;
|
|
45
|
+
|
|
46
|
+
let before_dev_command = config.build.as_ref().and_then(|b| b.before_dev_command.clone()).unwrap_or_else(|| "npm run dev".to_string());
|
|
47
|
+
let dev_url = config.build.as_ref().and_then(|b| b.dev_url.clone()).unwrap_or_else(|| "http://127.0.0.1:5173".to_string());
|
|
48
|
+
let port = dev_url.split(':').last().and_then(|p| p.parse().ok()).unwrap_or(5173);
|
|
49
|
+
|
|
50
|
+
// Read Cargo.toml to get bin name
|
|
51
|
+
let cargo_path = "src-ionyx/Cargo.toml";
|
|
52
|
+
if !Path::new(cargo_path).exists() {
|
|
53
|
+
return Err(anyhow::anyhow!("No {} found.", cargo_path));
|
|
54
|
+
}
|
|
55
|
+
let cargo_content = fs::read_to_string(cargo_path).await?;
|
|
56
|
+
let cargo: CargoToml = toml::from_str(&cargo_content)?;
|
|
57
|
+
let bin_name = cargo.package.name;
|
|
58
|
+
|
|
59
|
+
// Check if node_modules exists, if not, install dependencies
|
|
60
|
+
if !Path::new("node_modules").exists() {
|
|
61
|
+
println!("š¦ Installing dependencies...");
|
|
62
|
+
let status = Command::new("npm")
|
|
63
|
+
.arg("install")
|
|
64
|
+
.status()
|
|
65
|
+
.await?;
|
|
66
|
+
|
|
67
|
+
if !status.success() {
|
|
68
|
+
return Err(anyhow::anyhow!("npm install failed"));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
println!("š Starting frontend dev server: {}", before_dev_command.cyan());
|
|
73
|
+
let cmd_parts: Vec<&str> = before_dev_command.split_whitespace().collect();
|
|
74
|
+
let mut fe_cmd = if cfg!(target_os = "windows") {
|
|
75
|
+
let mut c = Command::new("cmd");
|
|
76
|
+
c.args(&["/C"]).args(&cmd_parts);
|
|
77
|
+
c
|
|
78
|
+
} else {
|
|
79
|
+
let mut c = Command::new(cmd_parts[0]);
|
|
80
|
+
c.args(&cmd_parts[1..]);
|
|
81
|
+
c
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
let mut fe_child = fe_cmd.spawn()?;
|
|
85
|
+
|
|
86
|
+
// Wait for server to be ready
|
|
87
|
+
println!("ā³ Waiting for frontend server to be ready on port {}...", port);
|
|
88
|
+
let mut attempts = 0;
|
|
89
|
+
let max_attempts = 60; // 30 seconds total (60 * 500ms)
|
|
90
|
+
|
|
91
|
+
while attempts < max_attempts {
|
|
92
|
+
match TcpStream::connect(&format!("127.0.0.1:{}", port)) {
|
|
93
|
+
Ok(_) => {
|
|
94
|
+
println!("ā
Frontend server is ready!");
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
Err(_) => {
|
|
98
|
+
attempts += 1;
|
|
99
|
+
if attempts % 10 == 0 {
|
|
100
|
+
println!(" Still waiting... ({}s)", (attempts * 500) / 1000);
|
|
101
|
+
}
|
|
102
|
+
tokio::time::sleep(Duration::from_millis(500)).await;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if attempts >= max_attempts {
|
|
108
|
+
return Err(anyhow::anyhow!("Frontend server failed to start within 30 seconds"));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
println!("š¦ Starting Rust backend host: {}", bin_name.cyan());
|
|
112
|
+
let mut be_cmd = Command::new("cargo");
|
|
113
|
+
be_cmd.args(&["run", "--bin", &bin_name]);
|
|
114
|
+
be_cmd.current_dir("src-ionyx");
|
|
115
|
+
|
|
116
|
+
// Set environment variable for backend
|
|
117
|
+
be_cmd.env("IONYX_URL", &format!("http://127.0.0.1:{}", port));
|
|
118
|
+
|
|
119
|
+
let mut be_child = be_cmd.spawn()?;
|
|
120
|
+
|
|
121
|
+
println!("\nš Ionyx is running!");
|
|
122
|
+
println!(" Frontend: http://127.0.0.1:{}", port);
|
|
123
|
+
println!(" Backend: src-ionyx ({})", bin_name);
|
|
124
|
+
println!("\nPress Ctrl+C to stop both processes.\n");
|
|
125
|
+
|
|
126
|
+
tokio::select! {
|
|
127
|
+
res = fe_child.wait() => {
|
|
128
|
+
let status = res?;
|
|
129
|
+
println!("Frontend process exited with status: {}", status);
|
|
130
|
+
}
|
|
131
|
+
res = be_child.wait() => {
|
|
132
|
+
let status = res?;
|
|
133
|
+
println!("Backend process exited with status: {}", status);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Kill the other process if one exits
|
|
138
|
+
let _ = fe_child.kill().await;
|
|
139
|
+
let _ = be_child.kill().await;
|
|
140
|
+
|
|
141
|
+
Ok(())
|
|
142
|
+
}
|