standard-plugin-cli 0.1.1

standard-plugin: scaffold, build, check and pack Standard Code plugins
Documentation
//! The manifests the SDK's guides and examples show are valid, the grants
//! guide lists every grant the catalogue knows, and every guide file is one
//! `standard-plugin guide` knows.

use std::path::{Path, PathBuf};

use standard_plugin_cli::check::check;
use standard_plugin_manifest::CATALOGUE;

/// The SDK crate beside this one, whose `docs/` and `examples/` these
/// tests read.
fn sdk() -> Option<PathBuf> {
    let root = Path::new(env!("CARGO_MANIFEST_DIR")).join("../standard-plugin-sdk");
    root.join("docs").is_dir().then_some(root)
}

/// The ```json blocks of a Markdown text (or of `//!` comments).
fn json_blocks(text: &str) -> Vec<String> {
    let mut blocks = Vec::new();
    let mut current: Option<String> = None;
    for line in text.lines() {
        let line = line
            .strip_prefix("//!")
            .map_or(line, |rest| rest.strip_prefix(' ').unwrap_or(rest));
        match (&mut current, line.trim_start()) {
            (None, "```json") => current = Some(String::new()),
            (Some(block), "```") => {
                blocks.push(std::mem::take(block));
                current = None;
            }
            (Some(block), _) => {
                block.push_str(line);
                block.push('\n');
            }
            _ => {}
        }
    }
    blocks
}

#[test]
fn documented_manifests_pass_check() {
    let Some(root) = sdk() else {
        eprintln!("skipped: the SDK crate is not beside this one");
        return;
    };
    let mut files: Vec<PathBuf> = Vec::new();
    for dir in ["docs", "examples"] {
        for entry in std::fs::read_dir(root.join(dir)).unwrap() {
            files.push(entry.unwrap().path());
        }
    }
    let mut checked = 0;
    for file in files {
        let text = std::fs::read_to_string(&file).unwrap();
        for block in json_blocks(&text) {
            if !block.contains("\"apiVersion\"") {
                continue;
            }
            let report = check(block.as_bytes(), None);
            assert!(report.is_ok(), "{}:\n{block}\n{report}", file.display());
            checked += 1;
        }
    }
    assert!(checked >= 5, "found only {checked} manifests");
}

#[test]
fn the_grants_page_lists_the_whole_catalogue() {
    let Some(root) = sdk() else {
        return;
    };
    let page = std::fs::read_to_string(root.join("docs/grants.md")).unwrap();
    for spec in CATALOGUE {
        assert!(
            page.contains(&format!("| `{}", spec.kind)),
            "docs/grants.md does not list `{}`",
            spec.kind
        );
    }
}

#[test]
fn every_guide_file_is_one_the_sdk_lists() {
    let Some(root) = sdk() else {
        return;
    };
    let mut files: Vec<String> = std::fs::read_dir(root.join("docs"))
        .unwrap()
        .map(|entry| entry.unwrap().path())
        .filter(|path| path.extension().is_some_and(|extension| extension == "md"))
        .map(|path| path.file_stem().unwrap().to_string_lossy().into_owned())
        .collect();
    files.sort();
    let mut listed: Vec<String> = standard_plugin::sources::GUIDES
        .iter()
        .map(|guide| guide.name.to_string())
        .collect();
    listed.sort();
    assert_eq!(
        files, listed,
        "docs/ and standard_plugin::sources::GUIDES differ"
    );
}