monochange_npm 0.5.1

Manage versions and releases for your multiplatform, multilanguage monorepo
Documentation
use monochange_config::load_workspace_configuration;
use monochange_test_helpers::fixture_path;
use serde_json::json;

use super::*;

fn npm_target(contents: &str, managed: bool, private: bool) -> LintTarget {
	LintTarget::new(
		Path::new(".").to_path_buf(),
		Path::new("./package.json").to_path_buf(),
		contents.to_string(),
		LintTargetMetadata {
			ecosystem: "npm".to_string(),
			relative_path: Path::new("package.json").to_path_buf(),
			package_name: Some("example".to_string()),
			package_id: managed.then(|| "example".to_string()),
			group_id: None,
			managed,
			private: Some(private),
			publishable: Some(!private),
		},
		Box::new(NpmLintFile {
			manifest: serde_json::from_str(contents).unwrap(),
			workspace_package_names: Arc::new(BTreeSet::from([
				"@scope/internal".to_string(),
				"shared".to_string(),
			])),
		}),
	)
}

fn config() -> LintRuleConfig {
	LintRuleConfig::Detailed {
		level: LintSeverity::Error,
		options: BTreeMap::from([("fix".to_string(), json!(true))]),
	}
}

#[test]
fn presets_are_exposed() {
	let presets = NpmLintSuite.presets();
	assert_eq!(presets.len(), 2);
	assert_eq!(
		presets.first().map(|preset| preset.id.as_str()),
		Some("npm/recommended")
	);
	assert_eq!(
		presets.get(1).map(|preset| preset.id.as_str()),
		Some("npm/strict")
	);
}

#[test]
fn workspace_protocol_rule_reports_internal_ranges() {
	let target = npm_target(
		r#"{
  "name": "example",
  "dependencies": {
    "@scope/internal": "^1.0.0"
  }
}"#,
		true,
		false,
	);
	let ctx = LintContext {
		workspace_root: &target.workspace_root,
		manifest_path: &target.manifest_path,
		contents: &target.contents,
		metadata: &target.metadata,
		parsed: target.parsed.as_ref(),
	};
	let results = WorkspaceProtocolRule::new().run(&ctx, &config());
	assert_eq!(results.len(), 1);
	assert!(
		results
			.first()
			.and_then(|result| result.fix.as_ref())
			.is_some()
	);
}

#[test]
fn sorted_dependencies_rule_reports_unsorted_sections() {
	let target = npm_target(
		r#"{
  "name": "example",
  "dependencies": {
    "zzz": "1",
    "aaa": "1"
  }
}"#,
		true,
		false,
	);
	let ctx = LintContext {
		workspace_root: &target.workspace_root,
		manifest_path: &target.manifest_path,
		contents: &target.contents,
		metadata: &target.metadata,
		parsed: target.parsed.as_ref(),
	};
	let results = SortedDependenciesRule::new().run(&ctx, &config());
	assert_eq!(results.len(), 1);
}

#[test]
fn required_package_fields_rule_supports_custom_fields() {
	let target = npm_target(r#"{"name":"example","description":"ok"}"#, true, false);
	let ctx = LintContext {
		workspace_root: &target.workspace_root,
		manifest_path: &target.manifest_path,
		contents: &target.contents,
		metadata: &target.metadata,
		parsed: target.parsed.as_ref(),
	};
	let config = LintRuleConfig::Detailed {
		level: LintSeverity::Error,
		options: BTreeMap::from([("fields".to_string(), json!(["description", "license"]))]),
	};
	let results = RequiredPackageFieldsRule::new().run(&ctx, &config);
	assert_eq!(results.len(), 1);
	assert!(
		results
			.first()
			.expect("expected lint result")
			.message
			.contains("license")
	);
}

#[test]
fn root_no_prod_deps_rule_moves_dependencies() {
	let target = npm_target(
		r#"{
  "name": "example",
  "dependencies": {
    "left-pad": "1"
  }
}"#,
		true,
		false,
	);
	let ctx = LintContext {
		workspace_root: &target.workspace_root,
		manifest_path: &target.manifest_path,
		contents: &target.contents,
		metadata: &target.metadata,
		parsed: target.parsed.as_ref(),
	};
	let results = RootNoProdDepsRule::new().run(&ctx, &config());
	assert_eq!(results.len(), 1);
	assert!(
		results
			.first()
			.and_then(|result| result.fix.as_ref())
			.is_some()
	);
}

#[test]
fn no_duplicate_dependencies_rule_prefers_dev_dependencies() {
	let target = npm_target(
		r#"{
  "name": "example",
  "dependencies": {
    "shared": "1"
  },
  "devDependencies": {
    "shared": "1"
  }
}"#,
		true,
		false,
	);
	let ctx = LintContext {
		workspace_root: &target.workspace_root,
		manifest_path: &target.manifest_path,
		contents: &target.contents,
		metadata: &target.metadata,
		parsed: target.parsed.as_ref(),
	};
	let results = NoDuplicateDependenciesRule::new().run(&ctx, &config());
	assert_eq!(results.len(), 1);
	assert!(
		results
			.first()
			.and_then(|result| result.fix.as_ref())
			.is_some()
	);
}

#[test]
fn unlisted_package_private_rule_reports_for_public_unmanaged_packages() {
	let target = npm_target(r#"{"name":"example"}"#, false, false);
	let ctx = LintContext {
		workspace_root: &target.workspace_root,
		manifest_path: &target.manifest_path,
		contents: &target.contents,
		metadata: &target.metadata,
		parsed: target.parsed.as_ref(),
	};
	let results = UnlistedPackagePrivateRule::new().run(&ctx, &config());
	assert_eq!(results.len(), 1);
	assert!(
		results
			.first()
			.and_then(|result| result.fix.as_ref())
			.is_some()
	);
}

#[test]
fn collect_targets_marks_configured_packages_as_managed() {
	let root = fixture_path!("cli-output/discover-mixed");
	let configuration = load_workspace_configuration(&root).unwrap();
	let targets = NpmLintSuite.collect_targets(&root, &configuration).unwrap();
	assert!(
		targets
			.iter()
			.all(|target| target.metadata.ecosystem == "npm")
	);
	assert!(targets.iter().any(|target| target.metadata.managed));
}