use serde::Deserialize;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Debug, Deserialize)]
struct CapacitorConfig {
#[serde(rename = "webDir")]
web_dir: String,
}
const RAW_FILE_TARGET: &str = "openharmony/entry/src/main/resources/rawfile/www";
const BRIDGE_FILES: &[&str] = &["cordova.js", "native-bridge.js", "cordova_plugins.js"];
fn find_project_root() -> Option<PathBuf> {
let cwd = env::current_dir().ok()?;
for ancestor in cwd.ancestors() {
if ancestor.join("capacitor.config.json").exists() {
return Some(ancestor.to_path_buf());
}
}
None
}
fn read_config(root: &Path) -> Result<CapacitorConfig, String> {
let path = root.join("capacitor.config.json");
let content = fs::read_to_string(&path)
.map_err(|e| format!("Failed to read {}: {}", path.display(), e))?;
serde_json::from_str(&content)
.map_err(|e| format!("Failed to parse capacitor.config.json: {}", e))
}
fn find_vite_out_dir(root: &Path) -> PathBuf {
let dist = root.join("dist");
if dist.is_dir() {
return dist;
}
let www = root.join("www");
if www.is_dir() {
return www;
}
dist
}
fn ohos_rawfile_dir(root: &Path) -> PathBuf {
root.join(RAW_FILE_TARGET)
}
fn copy_dir(src: &Path, dst: &Path) -> Result<(), String> {
if !src.is_dir() {
return Err(format!("Source directory does not exist: {}", src.display()));
}
fs::create_dir_all(dst)
.map_err(|e| format!("Failed to create {}: {}", dst.display(), e))?;
for entry in fs::read_dir(src).map_err(|e| format!("Failed to read {}: {}", src.display(), e))? {
let entry = entry.map_err(|e| format!("Dir entry error: {}", e))?;
let file_type = entry.file_type().map_err(|e| format!("File type error: {}", e))?;
let src_path = entry.path();
let name = entry.file_name();
let dst_path = dst.join(&name);
if file_type.is_dir() {
copy_dir(&src_path, &dst_path)?;
} else {
fs::copy(&src_path, &dst_path)
.map_err(|e| format!("Failed to copy {} -> {}: {}", src_path.display(), dst_path.display(), e))?;
}
}
Ok(())
}
fn has_bridge_file(dir: &Path, name: &str) -> bool {
dir.join(name).exists()
}
fn copy_bridge_from_node_modules(root: &Path, target: &Path, name: &str) -> Result<bool, String> {
let source = root
.join("node_modules")
.join("@capacitor-ohos")
.join("ohos")
.join("src")
.join("main")
.join("resources")
.join("rawfile")
.join(name);
if source.exists() {
fs::copy(&source, target.join(name))
.map_err(|e| format!("Failed to copy bridge file {}: {}", source.display(), e))?;
Ok(true)
} else {
Ok(false)
}
}
fn restore_from_git(root: &Path, target: &Path, name: &str) -> Result<bool, String> {
let rawfile_path = format!("openharmony/entry/src/main/resources/rawfile/www/{}", name);
let output = std::process::Command::new("git")
.args(["show", &format!("HEAD:{}", rawfile_path)])
.current_dir(root)
.output()
.map_err(|e| format!("Failed to run git show: {}", e))?;
if output.status.success() {
fs::write(target.join(name), &output.stdout)
.map_err(|e| format!("Failed to write restored file {}: {}", name, e))?;
Ok(true)
} else {
Ok(false)
}
}
fn ensure_bridge_files(root: &Path, target: &Path) -> Result<(), String> {
for &name in BRIDGE_FILES {
if has_bridge_file(target, name) {
println!(" ✓ {} (already present)", name);
continue;
}
if restore_from_git(root, target, name)? {
println!(" ✓ {} (restored from git)", name);
continue;
}
if copy_bridge_from_node_modules(root, target, name)? {
println!(" ✓ {} (copied from node_modules)", name);
continue;
}
println!(" ⚠ {} not found (skipped – may be optional)", name);
}
Ok(())
}
fn main() {
let root = find_project_root().unwrap_or_else(|| {
eprintln!("Error: cannot find capacitor.config.json in any parent directory.");
std::process::exit(1);
});
println!("Project root: {}", root.display());
println!();
let cfg = match read_config(&root) {
Ok(c) => c,
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
}
};
println!("Config: webDir = \"{}\"", cfg.web_dir);
let vite_out = find_vite_out_dir(&root);
if !vite_out.exists() {
eprintln!(
"Error: Vite build output not found at \"{}\".\n\
Run `npm run build` or `hionic buildui` first.",
vite_out.display()
);
std::process::exit(1);
}
println!("Vite build output: {}", vite_out.display());
let vite_out_name = vite_out
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("dist");
if cfg.web_dir != vite_out_name {
println!(
" ⚠ Mismatch: capacitor.config.json has webDir = \"{}\" but Vite builds to \"{}\".",
cfg.web_dir, vite_out_name
);
println!(
" → Suggestion: change webDir to \"{}\" in capacitor.config.json",
vite_out_name
);
}
let target = ohos_rawfile_dir(&root);
println!("Target: {}", target.display());
if target.exists() {
fs::remove_dir_all(&target)
.unwrap_or_else(|e| {
eprintln!("Warning: failed to remove old rawfile/www: {}", e);
});
}
fs::create_dir_all(&target)
.unwrap_or_else(|e| {
eprintln!("Error: failed to create {}: {}", target.display(), e);
std::process::exit(1);
});
println!("\nCopying web assets...");
if let Err(e) = copy_dir(&vite_out, &target) {
eprintln!("Error: {}", e);
std::process::exit(1);
}
println!("\nChecking bridge files...");
if let Err(e) = ensure_bridge_files(&root, &target) {
eprintln!("Error: {}", e);
std::process::exit(1);
}
println!("\n── Sync complete ──");
let file_count = match fs::read_dir(&target) {
Ok(entries) => entries.count(),
Err(_) => 0,
};
println!("Files deployed to {}: {}", target.display(), file_count);
println!("\nNext steps:");
println!(" 1. cd openharmony");
println!(" 2. hvigorw assembleHap");
println!(" 3. hionic run openharmony");
}