Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/cli/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
- [x] R / Stata runtime:补充运行时实现、codegen prompt 和执行注册(`src/runtime/`、`src/stage/implement.rs`、`src/stage/process.rs`)
- [x] doctor 检查表改由 runtime 注册表驱动,并覆盖已注册运行时(`src/doctor.rs`、`src/runtime/mod.rs`)
- [x] 全局 `--json` 错误通道和稳定错误码首切片(`src/main.rs`、`src/cli.rs`、`src/error.rs`)
- [ ] 成功命令统一结果模型:优先迁移无副作用查看命令和 catalog/pipeline 命令(`src/cli.rs`、`src/spec/`、`src/implementation/`)
- [x] 成功命令统一结果模型:优先迁移无副作用查看命令和 catalog/pipeline 命令(`src/cli.rs`、`src/spec/`、`src/implementation/`)

### Changed

Expand All @@ -30,8 +30,8 @@
- [x] 传输 provider 使用枚举或结构化解析,替代字符串匹配(`src/stage/transfer.rs`、`src/storage/mod.rs`)
- [x] process 使用结构化 Blueprint states 引用,替代逗号分隔的 pipeline 字符串(`src/stage/process.rs`)
- [x] 将命令分发移入库层,使 `Commands` 和统一参数注入路径可单测(`src/main.rs`、`src/cli.rs`、`src/lib.rs`)
- [ ] 补充 deploy、operate、monitor 阶段记录(`docs/`)
- [ ] 核对并补齐父仓库各平台 release workflow,确保 Linux、Windows、macOS 制品与发布记录一致(`../../.github/workflows/release-cli.yml`)
- [x] 补充 deploy、operate、monitor 阶段记录(`docs/`)
- [x] 核对并补齐父仓库各平台 release workflow,确保 Linux、Windows、macOS 制品与发布记录一致(`../../.github/workflows/release-cli.yml`)

## Provider 依赖项(不作为 0.3.0 发布门槛)

Expand Down
2 changes: 1 addition & 1 deletion src/cli/TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
- [x] 将 `run_command` 分发移入库层,使 `Commands` 和参数注入路径可单测(`src/cli.rs`、`src/lib.rs`)
- [x] 在 `src/stage/transfer.rs` 使用结构化 provider 枚举,替代字符串匹配
- [x] 在 `src/stage/process.rs` 使用结构化 Blueprint states 引用,替代逗号分隔字符串
- [ ] 将无副作用查看命令和 catalog/pipeline 命令迁移到统一成功结果模型(`src/cli.rs`、`src/spec/`、`src/implementation/`)
- [x] 将无副作用查看命令和 catalog/pipeline 命令迁移到统一成功结果模型(`src/cli.rs`、`src/spec/`、`src/implementation/`)

## 0.3.0 传输测试与发布

Expand Down
32 changes: 32 additions & 0 deletions src/cli/docs/deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# deploy — 发布状态

本页记录 `qtcloud-data` CLI 的发布与制品状态,写实为准。

## 当前状态

- 版本目标:`cli/v0.3.0`
- 发布入口:`qtcloud-devops release publish`
- 现在还没有正式发布完成

## 制品矩阵

`src/cli/Cargo.toml` 的 `cargo-dist` 目标是:

- `x86_64-unknown-linux-gnu`
- `x86_64-pc-windows-msvc`
- `aarch64-apple-darwin`
- `x86_64-apple-darwin`

对应的 release workflow 已补齐 Linux、Windows、macOS arm64、macOS Intel 的构建与上传条目。

## 发布审计

2026-08-24 的 `qtcloud-devops release audit -v cli/v0.3.0 --scope cli` 结果:

- 版本号格式:通过
- 配置文件一致性:通过
- CHANGELOG:通过
- 工作区状态:未通过,存在未提交变更
- 标签冲突:未通过,`cli/v0.3.0` 已存在
- GitHub Release:未通过,body 与 CHANGELOG 不同步

8 changes: 8 additions & 0 deletions src/cli/docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@

贡献与发布流程见 [CONTRIBUTING.md](../CONTRIBUTING.md)。

## 发布生命周期记录

| 文档 | 作用 |
|------|------|
| [deploy.md](deploy.md) | 发布状态、制品矩阵、当前 release audit 结果 |
| [operate.md](operate.md) | 安装与运行入口、可用 smoke 命令 |
| [monitor.md](monitor.md) | 发布后验证项、已完成检查与待办 |

## 命令结构总览

```
Expand Down
25 changes: 25 additions & 0 deletions src/cli/docs/monitor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# monitor — 发布后验证

本页记录发布后要看的验证项,以及当前已完成的本地检查。

## 已完成

- `cargo fmt --check`
- `cargo test --locked`
- `cargo clippy --locked -- -D warnings`
- `qtcloud-devops release audit -v cli/v0.3.0 --scope cli`

## 当前结果

- 格式检查:通过
- 测试:通过
- clippy:通过
- release audit:部分通过,仍有工作区、标签和 GitHub Release 不一致问题

## 发布后还要补

- `qtcloud-devops release status`
- `gh release view cli/v0.3.0`
- `cargo info qtcloud-data-cli --registry crates-io`
- `cargo install qtcloud-data-cli --version <X.Y.Z>`

25 changes: 25 additions & 0 deletions src/cli/docs/operate.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# operate — 安装与运行

本页记录本地安装和运行入口。

## 本地运行

```bash
cargo build --locked
qtcloud-data --help
qtcloud-data doctor --no-fail
qtcloud-data spec --help
qtcloud-data process --help
```

## 发布后安装

```bash
cargo install qtcloud-data-cli --version <X.Y.Z>
qtcloud-data --help
```

## 说明

以上入口用于确认二进制可启动、命令树可解析、基础 smoke 命令可执行。

81 changes: 44 additions & 37 deletions src/cli/src/implementation/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,15 +263,18 @@ pub fn run_with_mode(args: &CatalogArgs, mode: OutputMode) -> Result<(), CliErro
provider,
source,
artifact_type,
} => add(
} => add_with_mode(
path,
name.as_deref(),
provider.as_deref(),
source.as_deref(),
artifact_type.clone(),
mode,
),
CatalogAction::SetStatus { name, status } => set_status(name, status.clone()),
CatalogAction::Rm { name } => rm(name),
CatalogAction::SetStatus { name, status } => {
set_status_with_mode(name, status.clone(), mode)
}
CatalogAction::Rm { name } => rm_with_mode(name, mode),
}
}

Expand All @@ -281,14 +284,7 @@ fn list(mode: OutputMode) -> Result<(), CliError> {
if mode == OutputMode::Text {
println!("catalog 为空");
} else {
println!(
"{}",
serde_json::json!({
"ok": true,
"command": "catalog list",
"items": [],
})
);
crate::output::print_success("catalog list", serde_json::json!({"items": []}))?;
}
return Ok(());
}
Expand All @@ -308,14 +304,7 @@ fn list(mode: OutputMode) -> Result<(), CliError> {
}
OutputMode::Json => {
let items: Vec<_> = registry.entries().values().collect();
println!(
"{}",
serde_json::json!({
"ok": true,
"command": "catalog list",
"items": items,
})
);
crate::output::print_success("catalog list", serde_json::json!({"items": items}))?;
}
}
Ok(())
Expand All @@ -340,27 +329,23 @@ fn show(name: &str, mode: OutputMode) -> Result<(), CliError> {
println!("来源: {s}");
}
}
OutputMode::Json => println!(
"{}",
serde_json::json!({
"ok": true,
"command": "catalog show",
"volume": v,
})
),
OutputMode::Json => {
crate::output::print_success("catalog show", serde_json::json!({"volume": v}))?
}
}
Ok(())
}
None => Err(CliError::new(format!("未找到 volume: {name}"))),
}
}

fn add(
fn add_with_mode(
path_str: &str,
name: Option<&str>,
provider: Option<&str>,
source: Option<&str>,
artifact_type: VolumeArtifactType,
mode: OutputMode,
) -> Result<(), CliError> {
let volume = register_volume(RegisterVolume {
path: path_str,
Expand All @@ -371,13 +356,28 @@ fn add(
artifact_type,
})?;

println!("✓ 已注册 volume: {}", volume.name);
match mode {
OutputMode::Text => println!("✓ 已注册 volume: {}", volume.name),
OutputMode::Json => {
crate::output::print_success("catalog add", serde_json::json!({"volume": volume}))?
}
}
Ok(())
}

fn set_status(name: &str, status: VolumeStatus) -> Result<(), CliError> {
fn set_status_with_mode(
name: &str,
status: VolumeStatus,
mode: OutputMode,
) -> Result<(), CliError> {
let volume = set_volume_status_in(&util::catalog_dir(), name, status)?;
println!("✓ 已更新 volume 状态: {} -> {}", volume.name, volume.status);
match mode {
OutputMode::Text => println!("✓ 已更新 volume 状态: {} -> {}", volume.name, volume.status),
OutputMode::Json => crate::output::print_success(
"catalog set-status",
serde_json::json!({"volume": volume}),
)?,
}
Ok(())
}

Expand All @@ -400,11 +400,16 @@ pub fn set_volume_status_in(
Ok(volume)
}

fn rm(name: &str) -> Result<(), CliError> {
fn rm_with_mode(name: &str, mode: OutputMode) -> Result<(), CliError> {
let mut registry = open_registry();
match registry.remove(name) {
Ok(Some(_)) => {
println!("✓ 已删除 volume: {name}");
match mode {
OutputMode::Text => println!("✓ 已删除 volume: {name}"),
OutputMode::Json => {
crate::output::print_success("catalog rm", serde_json::json!({"name": name}))?
}
}
Ok(())
}
Ok(None) => Err(CliError::new(format!("未找到 volume: {name}"))),
Expand Down Expand Up @@ -623,12 +628,13 @@ mod tests {
unsafe {
std::env::set_var("CATALOG_DIR", &catalog_dir);
}
let err = add(
let err = add_with_mode(
"/nonexistent/file.csv",
None,
None,
None,
VolumeArtifactType::PreReview,
OutputMode::Text,
)
.unwrap_err();
unsafe {
Expand Down Expand Up @@ -730,12 +736,13 @@ mod tests {
unsafe {
std::env::set_var("CATALOG_DIR", &catalog_dir);
}
let result = add(
let result = add_with_mode(
file.to_str().unwrap(),
Some("DATA-1"),
Some("process"),
None,
VolumeArtifactType::PreReview,
OutputMode::Text,
);
unsafe {
std::env::remove_var("CATALOG_DIR");
Expand All @@ -756,7 +763,7 @@ mod tests {
std::env::set_var("CATALOG_DIR", &catalog_dir);
}
let name = seed_volume(&catalog_dir);
let result = rm(&name);
let result = rm_with_mode(&name, OutputMode::Text);
unsafe {
std::env::remove_var("CATALOG_DIR");
}
Expand Down Expand Up @@ -784,7 +791,7 @@ mod tests {
unsafe {
std::env::set_var("CATALOG_DIR", &catalog_dir);
}
let err = rm("ghost").unwrap_err();
let err = rm_with_mode("ghost", OutputMode::Text).unwrap_err();
unsafe {
std::env::remove_var("CATALOG_DIR");
}
Expand Down
39 changes: 13 additions & 26 deletions src/cli/src/implementation/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ fn cmd_list(dir: &str, mode: OutputMode) -> Result<(), CliError> {
let dir_path = Path::new(dir);
if dir_path.is_dir() {
let names = definition_names(dir_path);
render_list(&names, mode);
return Ok(());
return render_list(&names, mode);
}

let output = cue_export(&["export", "--out", "json", dir])
Expand All @@ -61,17 +60,15 @@ fn cmd_list(dir: &str, mode: OutputMode) -> Result<(), CliError> {
let value: Value = serde_json::from_slice(&output.stdout)
.map_err(|e| CliError::new(format!("cue 输出不是合法 JSON: {e}")))?;
let names = collect_defined_names(&value);
render_list(&names, mode);
Ok(())
render_list(&names, mode)
}

fn cmd_show(dir: &str, name: &str, mode: OutputMode) -> Result<(), CliError> {
let dir_path = Path::new(dir);
if let Some(path) = find_definition(dir_path, name) {
let content = std::fs::read_to_string(&path)
.map_err(|err| CliError::new(format!("读取 Pipeline 失败: {err}")))?;
render_show(name, &content, mode)?;
return Ok(());
return render_show(name, &content, mode);
}

let key = crate::util::to_camel(name);
Expand All @@ -84,26 +81,21 @@ fn cmd_show(dir: &str, name: &str, mode: OutputMode) -> Result<(), CliError> {
.map_err(|e| CliError::new(format!("cue 输出不是合法 JSON: {e}")))?;
let content = serde_json::to_string_pretty(&value)
.map_err(|e| CliError::new(format!("序列化失败: {e}")))?;
render_show(name, &content, mode)?;
Ok(())
render_show(name, &content, mode)
}

fn render_list(names: &[String], mode: OutputMode) {
fn render_list(names: &[String], mode: OutputMode) -> Result<(), CliError> {
match mode {
OutputMode::Text => {
println!("可用的 Pipeline:");
for name in names {
println!(" - {name}");
}
Ok(())
}
OutputMode::Json => {
crate::output::print_success("pipeline list", serde_json::json!({"items": names}))
}
OutputMode::Json => println!(
"{}",
serde_json::json!({
"ok": true,
"command": "pipeline list",
"items": names,
})
),
}
}

Expand All @@ -114,15 +106,10 @@ fn render_show(name: &str, content: &str, mode: OutputMode) -> Result<(), CliErr
let definition: serde_json::Value = serde_yaml::from_str(content)
.or_else(|_| serde_json::from_str(content))
.map_err(|err| CliError::new(format!("Pipeline 不是合法结构化数据: {err}")))?;
println!(
"{}",
serde_json::json!({
"ok": true,
"command": "pipeline show",
"name": name,
"pipeline": definition,
})
);
crate::output::print_success(
"pipeline show",
serde_json::json!({"name": name, "pipeline": definition}),
)?;
}
}
Ok(())
Expand Down
Loading
Loading