pray-core 1.9.0

Core library for Prayfile, the package manager for the language placed before inference
Documentation
use pray_core::environment::{package_matches_environment, validate_environment};
use pray_core::manifest::parse_manifest;
use pray_core::render::render_project;
use pray_core::resolve::resolve_project_in_context;
use pray_core::resolve_context::ResolveOptions;
use std::fs;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

fn unique_temp_dir(label: &str) -> PathBuf {
    let stamp = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .expect("clock")
        .as_nanos();
    std::env::temp_dir().join(format!("{label}-{stamp}"))
}

fn write_grouped_fixture(root: &Path) {
    fs::create_dir_all(root.join("packages/shared/exports")).expect("dirs");
    fs::create_dir_all(root.join("packages/dev-only/exports")).expect("dirs");
    fs::write(
        root.join("Prayfile"),
        r#"
prayfile "1"
target :tool_a do
  output "INSTRUCTIONS.md"
end
agent "sample/shared", "~> 1.0", path: "packages/shared"
group :development, :test do
  agent "sample/dev-only", "~> 1.0", path: "packages/dev-only"
end
"#,
    )
    .expect("prayfile");
    for (directory, package_name, note) in [
        ("shared", "sample/shared", "note for shared"),
        ("dev-only", "sample/dev-only", "note for dev-only"),
    ] {
        let package_root = root.join(format!("packages/{directory}"));
        fs::write(
            package_root.join(format!("{directory}.prayspec")),
            format!(
                r#"
Package::Specification.new do |spec|
  spec.name = "{package_name}"
  spec.version = "1.0.0"
  spec.summary = "fixture"
  spec.files = ["exports/note.md"]
  spec.exports = {{
    "note" => {{
      type: "fragment",
      path: "exports/note.md",
      summary: "note"
    }}
  }}
end
"#
            ),
        )
        .expect("prayspec");
        fs::write(package_root.join("exports/note.md"), format!("{note}\n")).expect("export");
    }
}

#[test]
fn grouped_packages_keep_membership_in_canonical_manifest() {
    let manifest = parse_manifest(
        r#"
prayfile "1"
group :development, :test do
  agent "sample/base", "*"
end
"#,
    )
    .expect("parse");
    assert_eq!(manifest.packages.len(), 1);
    assert_eq!(manifest.packages[0].groups, vec!["development", "test"]);
    let hash = manifest.manifest_hash().expect("hash");
    assert!(hash.starts_with("sha256:"));
}

#[test]
fn render_filters_packages_by_environment() {
    let root = unique_temp_dir("pray-env-render");
    write_grouped_fixture(&root);
    let options = ResolveOptions {
        environment: Some("development".to_string()),
        ..ResolveOptions::default()
    };
    let project =
        resolve_project_in_context(&root.join("Prayfile"), &root, &options).expect("resolve");
    assert_eq!(project.packages.len(), 2);
    let rendered = render_project(&project).expect("render");
    let content = &rendered[0].content;
    assert!(content.contains("note for shared"));
    assert!(content.contains("note for dev-only"));
}

#[test]
fn omitted_environment_renders_only_ungrouped_packages() {
    let root = unique_temp_dir("pray-env-default");
    write_grouped_fixture(&root);
    let project =
        resolve_project_in_context(&root.join("Prayfile"), &root, &ResolveOptions::default())
            .expect("resolve");
    let rendered = render_project(&project).expect("render");
    let content = &rendered[0].content;
    assert!(content.contains("note for shared"));
    assert!(!content.contains("note for dev-only"));
}

#[test]
fn unknown_environment_fails_with_available_groups() {
    let manifest = parse_manifest(
        r#"
prayfile "1"
group :development do
  agent "sample/base", "*"
end
"#,
    )
    .expect("parse");
    let error =
        validate_environment(&manifest, Some("production")).expect_err("unknown environment");
    assert!(error.to_string().contains("development"));
}

#[test]
fn package_matches_environment_supports_multiple_group_names() {
    assert!(package_matches_environment(
        &["development".to_string(), "test".to_string()],
        Some("test")
    ));
    assert!(!package_matches_environment(
        &["development".to_string()],
        None
    ));
}