nestrs-cli-rs 0.1.0

Rust port of the Nest CLI for the nestrs organization.
Documentation
use std::collections::BTreeMap;
use std::path::PathBuf;

use cli::add_action::{AddActionRequest, create_add_action_plan};
use cli::build_action::{BuildActionPlanRequest, create_build_action_plan};
use cli::commands::load_command_invocation;
use cli::configuration::{
    Builder, CompilerOptions, Configuration, GenerateOptions, GenerateSpec, ProjectConfiguration,
    SwcBuilderOptions, parse_configuration,
};
use cli::generate_action::{GenerateActionPlanOptions, build_generate_action_plan};
use cli::info_action::{
    PackageJsonDependency, PackageManagerInformation, SystemInformation, create_info_action_plan,
};
use cli::new_action::build_new_action_plan;
use cli::package_managers::{PackageManager, PackageManagerFactory};
use cli::schematics::{CollectionFactory, CollectionInstance, NestCollection, SchematicOption};
use cli::start_action::{DebugFlag, StartActionPlanRequest, create_start_action_plan};

#[test]
fn representative_new_and_generate_outputs_match_upstream_shape() {
    let new_invocation = load_command_invocation([
        "new",
        "Cats API",
        "--package-manager",
        "pnpm",
        "--strict",
        "--skip-git",
    ])
    .expect("new command");
    let new_plan =
        build_new_action_plan(&new_invocation.inputs, &new_invocation.options, "workspace")
            .expect("new plan");

    assert_eq!(new_plan.collection, "@nestrs/schematics");
    assert_eq!(new_plan.schematic, "application");
    assert_eq!(new_plan.project_directory, PathBuf::from("cats-api"));
    assert_eq!(
        new_plan.schematic_command,
        "@nestrs/schematics:application --name=cats-api --no-dry-run --skip-git --strict --package-manager=\"pnpm\" --collection=\"@nestrs/schematics\" --language=\"rs\""
    );
    assert_eq!(
        new_plan
            .install_command
            .as_ref()
            .map(|command| command.raw_full_command()),
        Some("pnpm install --strict-peer-dependencies=false --reporter=silent".to_string())
    );
    assert!(new_plan.initialize_git_command.is_none());

    let configuration = Configuration {
        generate_options: GenerateOptions {
            spec: Some(GenerateSpec::BySchematic(BTreeMap::from([(
                "service".to_string(),
                false,
            )]))),
            flat: Some(true),
            spec_file_suffix: Some("test".to_string()),
            base_dir: Some("features".to_string()),
        },
        ..Configuration::default()
    };
    let generate_invocation =
        load_command_invocation(["generate", "service", "Cats", "domain/cats"])
            .expect("generate command");
    let mut generate_inputs = generate_invocation.inputs;
    generate_inputs.extend(generate_invocation.options);
    let generate_plan = build_generate_action_plan(
        &generate_inputs,
        &configuration,
        GenerateActionPlanOptions::default(),
    )
    .expect("generate plan");

    assert_eq!(generate_plan.source_root, PathBuf::from("src/features"));
    assert_eq!(generate_plan.spec, false);
    assert_eq!(generate_plan.flat, true);
    assert_eq!(
        generate_plan.schematic_command,
        "@nestrs/schematics:service --name=cats --path=domain/cats --no-dry-run --no-skip-import --language=\"rs\" --source-root=\"src\\features\" --no-spec --flat --spec-file-suffix=\"test\""
    );
}

#[test]
fn representative_build_and_start_plans_match_upstream_shape() {
    let mut projects = BTreeMap::new();
    projects.insert(
        "api".to_string(),
        ProjectConfiguration {
            source_root: Some("apps/api/src".to_string()),
            entry_file: Some("server".to_string()),
            compiler_options: Some(CompilerOptions {
                builder: Builder::Swc(SwcBuilderOptions {
                    out_dir: Some("build/api".to_string()),
                    ..SwcBuilderOptions::default()
                }),
                manual_restart: true,
                ..CompilerOptions::default()
            }),
            ..ProjectConfiguration::default()
        },
    );

    let build_invocation =
        load_command_invocation(["build", "api", "--watch", "--builder", "cargo"])
            .expect("build command");
    let build_plan = create_build_action_plan(BuildActionPlanRequest {
        cwd: PathBuf::from("workspace"),
        configuration: Configuration {
            projects: projects.clone(),
            ..Configuration::default()
        },
        command_inputs: build_invocation.inputs,
        command_options: build_invocation.options,
        ts_build_info_file: Some(PathBuf::from("build/.tsbuildinfo")),
    })
    .expect("build plan");

    assert_eq!(build_plan.app_names, vec![Some("api".to_string())]);
    assert!(build_plan.watch_mode);
    assert_eq!(
        build_plan.project_plans[0].build_plan.inputs.output_dir,
        PathBuf::from("build/api")
    );

    let start_invocation = load_command_invocation([
        "start",
        "api",
        "--watch",
        "--debug",
        "127.0.0.1:9229",
        "--env-file",
        ".env",
        "--no-shell",
        "--",
        "--trace-warnings",
    ])
    .expect("start command");
    let start_plan = create_start_action_plan(StartActionPlanRequest {
        cwd: PathBuf::from("workspace"),
        configuration: Configuration {
            projects,
            ..Configuration::default()
        },
        command_inputs: start_invocation.inputs,
        command_options: start_invocation.options,
        extra_flags: start_invocation.extra_flags,
        ts_build_info_file: None,
    })
    .expect("start plan");

    assert_eq!(start_plan.app_name, Some("api".to_string()));
    assert_eq!(
        start_plan.process_plan.debug_flag,
        Some(DebugFlag::InspectAddress("127.0.0.1:9229".to_string()))
    );
    assert!(!start_plan.process_plan.shell);
    assert_eq!(
        start_plan.process_plan.manifest_path,
        Some(PathBuf::from("apps").join("api").join("Cargo.toml"))
    );
    assert_eq!(
        start_plan.process_plan.source_root_command.command,
        "run --manifest-path apps\\api\\Cargo.toml -- --trace-warnings"
    );
}

#[test]
fn representative_add_and_info_outputs_match_upstream_shape() {
    let configuration = Configuration {
        projects: BTreeMap::from([(
            "api".to_string(),
            ProjectConfiguration {
                source_root: Some("apps/api/src".to_string()),
                ..ProjectConfiguration::default()
            },
        )]),
        ..Configuration::default()
    };
    let add_plan = create_add_action_plan(AddActionRequest {
        library: "@nestrs/swagger@next".to_string(),
        skip_install: false,
        dry_run: false,
        project: Some("api".to_string()),
        source_root: None,
        extra_flags: vec!["--document-builder=true".to_string()],
        package_manager: PackageManager::Npm,
        configuration,
    });

    assert_eq!(add_plan.package_name, "@nestrs/swagger");
    assert_eq!(add_plan.collection_name, "@nestrs/swagger");
    assert_eq!(add_plan.tag_name, "next");
    assert_eq!(
        add_plan
            .install_command
            .as_ref()
            .map(|command| command.raw_full_command()),
        None
    );
    assert_eq!(
        add_plan.schematic_command,
        "@nestrs/swagger:nest-add --source-root=\"apps/api/src\" --document-builder=true"
    );

    let info_plan = create_info_action_plan(
        SystemInformation {
            os_version: "Windows 10.0".to_string(),
            node_version: "v20.11.0".to_string(),
        },
        PackageManagerInformation {
            name: "NPM".to_string(),
            version: Some("10.0.0".to_string()),
        },
        "11.0.21",
        &[
            PackageJsonDependency {
                package_name: "@nestrs/core".to_string(),
                declared_version: "^11.0.0".to_string(),
                resolved_version: Some("11.0.1".to_string()),
            },
            PackageJsonDependency {
                package_name: "@nestrs/common".to_string(),
                declared_version: "~10.0.0".to_string(),
                resolved_version: None,
            },
        ],
    );

    assert_eq!(info_plan.cli_version, "11.0.21");
    assert_eq!(
        info_plan
            .nest_dependencies
            .iter()
            .map(|dependency| dependency.package_name.as_str())
            .collect::<Vec<_>>(),
        vec!["@nestrs/common", "@nestrs/core"]
    );
    assert!(info_plan.warnings.contains_key("10"));
    assert!(info_plan.warnings.contains_key("11"));
}

#[test]
fn representative_configuration_package_manager_and_schematic_outputs_match_upstream_shape() {
    let configuration = parse_configuration(
        r#"{
            "language": "ts",
            "sourceRoot": "apps/api/src",
            "compilerOptions": {
                "builder": {
                    "type": "swc",
                    "options": { "outDir": "build" }
                }
            }
        }"#,
    )
    .expect("configuration");

    assert_eq!(configuration.language, "ts");
    assert_eq!(configuration.source_root, "apps/api/src");
    assert_eq!(
        configuration.compiler_options.builder,
        Builder::Swc(SwcBuilderOptions {
            out_dir: Some("build".to_string()),
            ..SwcBuilderOptions::default()
        })
    );

    let pnpm = PackageManagerFactory::create_manager(PackageManager::Pnpm);
    assert_eq!(
        pnpm.add_production_command(&["@nestrs/swagger"], "latest")
            .raw_full_command(),
        "pnpm install --strict-peer-dependencies=false --save @nestrs/swagger@latest"
    );

    assert!(matches!(
        CollectionFactory::create("@nestrs/schematics"),
        CollectionInstance::Nest(_)
    ));
    assert_eq!(
        NestCollection::new().execute_command(
            "co",
            &[
                SchematicOption::new("name", "CatsController"),
                SchematicOption::new("sourceRoot", "apps/api/src"),
                SchematicOption::new("spec", false),
            ],
        ),
        Ok("@nestrs/schematics:controller --name=cats-controller --source-root=\"apps/api/src\" --no-spec".to_string())
    );
}