feat(init): 支持补齐内置模板空目录

新增 init 子命令,根据项目中的 world_*_packs.json 解析实际包目录并创建标准空目录。

改用 .empty-dirs 维护内置模板空目录清单,删除会污染用户项目和网易 Bedrock 加载流程的 .gitkeep 占位文件。
This commit is contained in:
2026-05-14 22:16:18 +08:00
parent 55e92c4b4f
commit de2b804aad
42 changed files with 197 additions and 7 deletions

67
src/commands/init.rs Normal file
View File

@@ -0,0 +1,67 @@
use std::fs;
use crate::commands::InitArgs;
use crate::entity;
use crate::error::{CliError, Result};
use crate::utils::file;
include!(concat!(env!("OUT_DIR"), "/embedded_examples.rs"));
pub fn execute(args: &InitArgs) {
if let Err(e) = run_init(args) {
eprintln!("❌ 项目初始化失败: {}", e);
return;
}
println!("🍀 项目初始化完成");
}
fn run_init(args: &InitArgs) -> Result<()> {
let project_dir = file::find_project_dir(&args.path)?;
let target = args.target.as_deref().unwrap_or("default");
let empty_dirs = lookup_empty_dirs(target)?;
let release_info = entity::get_current_release_info(&project_dir)?;
let behavior_pack = format!("behavior_pack_{}", release_info.behavior_identifier);
let resource_pack = format!("resource_pack_{}", release_info.resource_identifier);
let mut created = 0usize;
let mut skipped = 0usize;
for rel in empty_dirs {
let rewritten = rewrite_pack_path(rel, &behavior_pack, &resource_pack);
let abs = project_dir.join(&rewritten);
if abs.is_dir() {
skipped += 1;
continue;
}
fs::create_dir_all(&abs).map_err(|e| file::io_error("创建目录", &abs, e))?;
println!("📁 创建目录: {}", rewritten);
created += 1;
}
println!("✅ 新建 {} 个目录, 跳过 {} 个已存在目录", created, skipped);
Ok(())
}
fn lookup_empty_dirs(target: &str) -> Result<&'static [&'static str]> {
EMBEDDED_EXAMPLE_EMPTY_DIRS
.iter()
.find(|e| e.example == target)
.map(|e| e.dirs)
.ok_or_else(|| {
CliError::NotFound(format!(
"example target '{}' was not found in the built-in examples",
target
))
})
}
fn rewrite_pack_path(rel: &str, behavior: &str, resource: &str) -> String {
if let Some(rest) = rel.strip_prefix("behavior_pack/") {
format!("{}/{}", behavior, rest)
} else if let Some(rest) = rel.strip_prefix("resource_pack/") {
format!("{}/{}", resource, rest)
} else {
rel.to_string()
}
}

View File

@@ -2,6 +2,7 @@ use clap::{Args, Parser, Subcommand, arg};
pub mod components;
pub mod create;
pub mod init;
pub mod release;
#[derive(Parser)]
@@ -24,6 +25,8 @@ pub enum Commands {
Release(ReleaseArgs),
/// Create a new mod project
Create(CreateArgs),
/// Initialize standard empty directories for an existing project
Init(InitArgs),
/// Create a new component
Components(ComponentsArgs),
}
@@ -52,6 +55,16 @@ pub struct CreateArgs {
pub target: Option<String>,
}
#[derive(Args)]
pub struct InitArgs {
/// The path of the project (default: current directory)
#[arg(short, long)]
pub path: Option<String>,
/// Example target whose layout to apply
#[arg(short, long)]
pub target: Option<String>,
}
#[derive(Args)]
pub struct ComponentsArgs {
/// The path of the project

View File

@@ -12,6 +12,7 @@ fn main() {
match &cli.command {
Commands::Release(args) => commands::release::execute(args),
Commands::Create(args) => commands::create::execute(args),
Commands::Init(args) => commands::init::execute(args),
Commands::Components(args) => commands::components::execute(args),
}
}