feat(bat): 补全工作流校验与调度过滤
bat-rust / Build and test Rust (push) Canceled after 0s
bat-rust / Build and test Go API (push) Canceled after 0s

新增 parse clear-cache 和 i18n validate,补齐 schedule 的作用域过滤与单轮执行上限,并将列表过滤参数暴露给 bat-api dashboard。同步 RPC、OpenAPI、用户文档和回归测试。

Refs #43
This commit is contained in:
2026-08-03 23:47:59 +08:00
parent 0784d5b532
commit 1933d6acb0
19 changed files with 714 additions and 49 deletions
+96 -14
View File
@@ -13,9 +13,9 @@ use bat_infrastructure::{
read_file_no_symlink, read_localized_patch_manifest_at, read_localized_version_state,
read_parse_cache_at, read_snapshot, read_textunit_index_at, read_translation_workbench,
read_version_state, redact_proxy_url, repack_bundle, resolve_curl_proxy, set_translation,
validate_output_root, validate_runtime_state_dir, write_file_atomic,
write_official_textunit_queues, CurlProxyConfig, CurlProxyMode, LocalizedPatchConfig,
LocalizedPatchReport, LocalizedPatchService, OfficialEndpointMarkerRole,
validate_output_root, validate_runtime_state_dir, validate_translation_workbench,
write_file_atomic, write_official_textunit_queues, CurlProxyConfig, CurlProxyMode,
LocalizedPatchConfig, LocalizedPatchReport, LocalizedPatchService, OfficialEndpointMarkerRole,
OfficialFailedVersionRecord, OfficialParseCacheService, OfficialParseConfig,
OfficialResourceHashVerification, OfficialResourceVerification, OfficialServerInfoSource,
OfficialTextUnitQuery, OfficialTextUnitTaskQuery, OfficialUpdateConfig, OfficialUpdateProgress,
@@ -24,10 +24,10 @@ use bat_infrastructure::{
PatchApplyParams, PatchApplyReport, ReleaseFlowStatusCode, RepackReport,
SqliteResourceRepository, SqliteTranslationTaskRepository, TranslationTaskStatus,
UnityFsFieldPatchParams, UnityFsPatchReport, UnityFsStringFieldPatchParams,
UnityFsTextAssetPatchParams, LOCALIZED_CURRENT_LINK, LOCALIZED_PATCH_MANIFEST_FILE,
LOCALIZED_VERSIONS_DIR, LOCALIZED_VERSION_STATE_FILE, MAX_DOWNLOAD_CONCURRENCY,
MIN_DOWNLOAD_CONCURRENCY, OFFICIAL_PARSE_CACHE_FILE, OFFICIAL_TEXTUNIT_INDEX_FILE,
PRIVATE_FILE_MODE,
UnityFsTextAssetPatchParams, CROWDIN_TEXTUNIT_QUEUE_FILE, LOCALIZED_CURRENT_LINK,
LOCALIZED_PATCH_MANIFEST_FILE, LOCALIZED_VERSIONS_DIR, LOCALIZED_VERSION_STATE_FILE,
MAX_DOWNLOAD_CONCURRENCY, MIN_DOWNLOAD_CONCURRENCY, OFFICIAL_PARSE_CACHE_FILE,
OFFICIAL_TEXTUNIT_INDEX_FILE, OFFICIAL_TEXTUNIT_TASK_QUEUE_FILE, PRIVATE_FILE_MODE,
};
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};
@@ -79,7 +79,8 @@ use translation_query::{
update_translation_task_status_report,
};
use workflow_commands::{
run_parse_once, run_publish_localized, run_repack, run_translate_once, run_translation_set,
run_parse_clear_cache, run_parse_once, run_publish_localized, run_repack, run_translate_once,
run_translation_set, run_translation_validate,
};
const EXIT_ERROR: i32 = 1;
@@ -192,10 +193,18 @@ fn run() -> anyhow::Result<i32> {
run_repeated_workflow(&options, "parse", run_parse_once)?;
Ok(0)
}
CliCommand::ParseClearCache => {
run_parse_clear_cache(&options)?;
Ok(0)
}
CliCommand::Translate => {
run_repeated_workflow(&options, "translate", run_translate_once)?;
Ok(0)
}
CliCommand::TranslationValidate => {
run_translation_validate(&options)?;
Ok(0)
}
CliCommand::TranslationSet => {
run_translation_set(&options)?;
Ok(0)
@@ -356,6 +365,7 @@ struct CliOptions {
schedule_delay: Option<Duration>,
schedule_every: Option<Duration>,
schedule_count: Option<usize>,
schedule_max_runs: Option<usize>,
schedule_args: Vec<String>,
schedule_clear_args: bool,
schedule_clear_every: bool,
@@ -438,6 +448,7 @@ impl Default for CliOptions {
schedule_delay: None,
schedule_every: None,
schedule_count: None,
schedule_max_runs: None,
schedule_args: Vec::new(),
schedule_clear_args: false,
schedule_clear_every: false,
@@ -504,7 +515,9 @@ enum CliCommand {
Run,
Pull,
Parse,
ParseClearCache,
Translate,
TranslationValidate,
TranslationSet,
Repack,
PublishLocalized,
@@ -1762,11 +1775,31 @@ fn dispatch_rpc_method(
"resource.state",
build_resource_state_report(state_dir),
),
RPC_METHOD_SCHEDULE_LIST => rpc_envelope_from_result(
request_id,
RPC_METHOD_SCHEDULE_LIST,
schedule_commands::schedule_list_report(state_dir),
),
RPC_METHOD_SCHEDULE_LIST => {
let params = request
.params
.as_ref()
.filter(|params| !params.is_null())
.map(|params| serde_json::from_value(params.clone()))
.transpose()
.map_err(|error| {
ApiError::new(
ErrorCode::RPC_INVALID_PARAMS,
RPC_METHOD_SCHEDULE_LIST,
format!("params 无效:{error}"),
)
});
let params = match params {
Ok(Some(params)) => params,
Ok(None) => schedule_commands::ScheduleListRequest::default(),
Err(error) => return rpc_envelope_error(request_id, error),
};
rpc_envelope_from_result(
request_id,
RPC_METHOD_SCHEDULE_LIST,
schedule_commands::schedule_list_report_with_request(state_dir, params),
)
}
RPC_METHOD_SCHEDULE_ADD => {
let params = match rpc_struct_params::<schedule_commands::ScheduleMutationRequest>(
request.params.as_ref(),
@@ -6754,6 +6787,16 @@ fn parse_args_with_env(
options.schedule_count = Some(value);
options.schedule_option_explicit = true;
}
"--schedule-max-runs" => {
let value = next_option_value(&mut args, &flag)?
.parse::<usize>()
.map_err(|error| anyhow::anyhow!("{flag} 无效:{error}"))?;
if value == 0 {
return Err(anyhow::anyhow!("--schedule-max-runs 必须大于 0"));
}
options.schedule_max_runs = Some(value);
options.schedule_option_explicit = true;
}
"--schedule-arg" => {
options
.schedule_args
@@ -7120,11 +7163,40 @@ fn parse_args_with_env(
));
}
}
CliCommand::ParseClearCache => {
if options.watch || options.daemon || options.daemon_child {
return Err(anyhow::anyhow!("parse clear-cache 只支持单次执行"));
}
if !options.config.force {
return Err(anyhow::anyhow!(
"parse clear-cache 是破坏性操作,必须显式指定 --force"
));
}
if options.config.dry_run || options.run_count.is_some() {
return Err(anyhow::anyhow!(
"parse clear-cache 不支持 --dry-run 或 --run-count"
));
}
options.progress = false;
options.banner = false;
}
CliCommand::TranslationValidate => {
if options.watch || options.daemon || options.daemon_child {
return Err(anyhow::anyhow!("i18n validate 只支持单次执行"));
}
if options.config.force || options.config.dry_run || options.run_count.is_some() {
return Err(anyhow::anyhow!(
"i18n validate 不支持 --force、--dry-run 或 --run-count"
));
}
options.progress = false;
options.banner = false;
}
CliCommand::TranslationSet | CliCommand::Repack => {
if options.watch || options.daemon || options.daemon_child {
return Err(anyhow::anyhow!("translation set/repack 只支持单次执行"));
}
if options.config.force || options.sync_option_explicit {
if options.config.force || options.sync_option_explicit || options.run_count.is_some() {
return Err(anyhow::anyhow!("translation set/repack 不接受资源同步选项"));
}
options.progress = false;
@@ -7151,6 +7223,11 @@ fn parse_args_with_env(
if options.interval_explicit && !matches!(options.command, CliCommand::ScheduleRun) {
return Err(anyhow::anyhow!("--interval 只适用于 schedule run 的轮询"));
}
if options.schedule_max_runs.is_some()
&& !matches!(options.command, CliCommand::ScheduleRun)
{
return Err(anyhow::anyhow!("--schedule-max-runs 只适用于 schedule run"));
}
options.progress = false;
options.banner = false;
}
@@ -7294,6 +7371,7 @@ fn parse_parse_command(
"status" => CliCommand::ParseStatus,
"text-units" => CliCommand::ParseTextUnits,
"errors" => CliCommand::ParseErrors,
"clear-cache" => CliCommand::ParseClearCache,
"schedule" => {
options.schedule_group = Some("parse".to_string());
return parse_schedule_command(args, options);
@@ -7315,6 +7393,7 @@ fn parse_translation_command(
"run" => CliCommand::Translate,
"export" => CliCommand::Translate,
"set" => CliCommand::TranslationSet,
"validate" => CliCommand::TranslationValidate,
"publish" => CliCommand::PublishLocalized,
"tasks" => CliCommand::TranslationTasks,
"handoff" => CliCommand::TranslationHandoff,
@@ -7435,10 +7514,12 @@ fn print_usage(binary: &str) {
eprintln!(" res pull Pull official resources once or repeatedly");
eprintln!(" res schedule Manage resource pull schedules (CLI/RPC/dashboard)");
eprintln!(" parse run Parse current official release");
eprintln!(" parse clear-cache Clear regenerable parse and translation queue files");
eprintln!(" parse repack Repack a UnityFS bundle from a JSON spec");
eprintln!(" i18n run Refresh offline translation work");
eprintln!(" i18n export Export an editable translation workbench");
eprintln!(" i18n set Update one translation workbench entry");
eprintln!(" i18n validate Validate workbench against the current official release");
eprintln!(" i18n publish Publish a localized release");
eprintln!(" i18n schedule Manage translation schedules");
eprintln!(" refresh Run one update check, or ask a live daemon to refresh");
@@ -7589,6 +7670,7 @@ fn print_usage(binary: &str) {
eprintln!(" --schedule-delay <DURATION> Delay first execution from now");
eprintln!(" --schedule-every <DURATION> Period between executions");
eprintln!(" --schedule-count <N> Bounded execution count");
eprintln!(" --schedule-max-runs <N> Maximum plans executed by one schedule run");
eprintln!(" --schedule-arg <ARG> Argument passed to scheduled child command");
eprintln!(" --schedule-clear-args Clear args during schedule update");
eprintln!(" --schedule-clear-every Convert a periodic plan to one-shot");