nichlink-cli 0.1.3

NichLink command-line interface: project scaffolding, Studio, and the MCP bridge
Documentation
//! `nichlink check`: run the registration discovery and validation pass.
//! `nichlink check`:运行注册发现与校验。
//!
//! Split out of `lib.rs`: this command owns the `--json` document contract and
//! the human line, while the shared `resolve_package`/`build_out_dir` helpers it
//! calls stay in the dispatching parent so `check`, `explain` and `grafts` all
//! read the same package root and output directory.
//! 从 `lib.rs` 拆出:本命令拥有 `--json` 文档契约与人类可读行,而它调用的共用
//! `resolve_package`/`build_out_dir` 辅助函数留在分发父模块,使 `check`、`explain`
//! 与 `grafts` 读取同一个包根与输出目录。

use std::io::Write;

use super::{build_out_dir, resolve_package};

/// Run the validation pass, optionally as one JSON document for CI.
/// 运行校验,可选地以单个 JSON 文档输出给 CI。
///
/// `--json` keeps stdout reserved for exactly one document: success is the
/// empty diagnostics document, failure is the same document with every
/// diagnostic, and the non-zero exit still comes from the returned `Err`. A
/// human run stays byte-identical to the historical output.
/// `--json` 让 stdout 只保留一个文档:成功就是空的诊断文档,失败是带全部诊断的同一
/// 文档,而非零退出仍由返回的 `Err` 给出。人类可读运行与历史输出逐字节一致。
pub(crate) fn check(
    args: &mut impl Iterator<Item = String>,
    out: &mut dyn Write,
) -> Result<(), String> {
    let mut json_output = false;
    let mut directory: Option<String> = None;
    for arg in args.by_ref() {
        match arg.as_str() {
            "--json" => json_output = true,
            _ if arg.starts_with('-') => return Err(format!("unexpected argument '{arg}'")),
            _ if directory.is_none() => directory = Some(arg),
            _ => return Err("check accepts at most one path".to_owned()),
        }
    }
    let directory = directory.unwrap_or_else(|| ".".to_owned());
    let (manifest, package) = match resolve_package(&directory) {
        Ok(resolved) => resolved,
        Err(error) => {
            // The `--json` contract is "stdout is one JSON document"; a
            // resolution failure used to return before writing anything, so a
            // machine reader got an empty stream instead of a document that
            // names the failure. The document keeps the success shape.
            // `--json` 契约是"stdout 是一个 JSON 文档";解析失败此前在写出任何东西之前
            // 就返回,机器读者拿到的是空流,而不是点名失败的文档。该文档保持成功时的形状。
            if json_output {
                let mut diagnostics = nichlink::BuildDiagnostics::default();
                diagnostics.push(nichlink::BuildDiagnostic::new("resolve", error.clone()));
                writeln!(out, "{}", diagnostics.to_json())
                    .map_err(|error| format!("cannot write output: {error}"))?;
            }
            return Err(error);
        }
    };
    let out_dir = build_out_dir(&manifest);
    if json_output {
        return match nichlink_build_method::check_for(&manifest, &out_dir, &package) {
            Ok(()) => {
                writeln!(out, "{}", nichlink::BuildDiagnostics::default().to_json())
                    .map_err(|error| format!("cannot write output: {error}"))?;
                Ok(())
            }
            Err(diagnostics) => {
                writeln!(out, "{}", diagnostics.to_json())
                    .map_err(|error| format!("cannot write output: {error}"))?;
                Err(format!(
                    "registration check failed ({} diagnostic(s); JSON on stdout)",
                    diagnostics.len()
                ))
            }
        };
    }
    nichlink_build_method::run_for(&manifest, &out_dir, &package)?;
    writeln!(out, "nichlink check: ok ({package})")
        .map_err(|error| format!("cannot write output: {error}"))?;
    Ok(())
}