monochange 0.6.6

Manage versions and releases for your multiplatform, multilanguage monorepo
Documentation
use std::fs;

use super::*;

fn write_file(root: &std::path::Path, relative_path: &str, contents: &str) {
	let path = root.join(relative_path);
	if let Some(parent) = path.parent() {
		fs::create_dir_all(parent).unwrap_or_else(|error| panic!("create parent: {error}"));
	}
	fs::write(&path, contents).unwrap_or_else(|error| panic!("write {}: {error}", path.display()));
}

#[test]
fn audit_migration_detects_legacy_release_tooling() {
	let tempdir = tempfile::tempdir().unwrap_or_else(|error| panic!("tempdir: {error}"));
	let root = tempdir.path();

	for path in [
		"monochange.toml",
		"knope.toml",
		".knope.toml",
		".changeset/config.json",
		"release-please-config.json",
		".release-please-manifest.json",
		".releaserc",
		".releaserc.json",
		".releaserc.yml",
		".releaserc.yaml",
		".releaserc.js",
		"release.config.js",
		"release.toml",
		"mdt.toml",
		"CHANGELOG.md",
		"changelog.md",
	] {
		write_file(root, path, "configured\n");
	}

	write_file(
		root,
		"package.json",
		r#"{
			"devDependencies": {
				"@changesets/cli": "latest",
				"release-please": "latest",
				"semantic-release": "latest",
				"knope": "latest"
			},
			"scripts": { "changeset": "changeset" }
		}"#,
	);
	write_file(root, ".github/workflows/ignore.txt", "semantic-release\n");
	write_file(
		root,
		".github/workflows/release.yml",
		"changesets/action\nrelease-please\nsemantic-release\nknope\ncargo release\n",
	);

	let report = audit_migration(root).unwrap_or_else(|error| panic!("audit migration: {error}"));

	assert_eq!(report.status, MigrationAuditStatus::MigrationNeeded);
	assert!(
		report
			.signals
			.iter()
			.any(|signal| signal.kind == "monochange-config")
	);
	assert!(
		report.signals.iter().any(|signal| {
			signal.kind == "legacy-changeset-tool" && signal.tool == "changesets"
		})
	);
	assert!(report.signals.iter().any(|signal| {
		signal.kind == "legacy-release-tool" && signal.tool == "semantic-release"
	}));
	assert!(
		report
			.signals
			.iter()
			.any(|signal| { signal.kind == "package-script" && signal.tool == "release-please" })
	);
	assert!(report.signals.iter().any(|signal| {
		signal.kind == "ci-workflow"
			&& signal.tool == "cargo-release"
			&& signal.path == ".github/workflows/release.yml"
	}));
	assert!(
		!report
			.signals
			.iter()
			.any(|signal| signal.path.ends_with("ignore.txt"))
	);

	let recommendation_ids = report
		.recommendations
		.iter()
		.map(|recommendation| recommendation.id.as_str())
		.collect::<Vec<_>>();
	assert!(!recommendation_ids.contains(&"generate-config"));
	assert!(recommendation_ids.contains(&"translate-release-tooling"));
	assert!(recommendation_ids.contains(&"audit-changelogs"));
	assert!(recommendation_ids.contains(&"replace-ci-workflows"));
	assert!(recommendation_ids.contains(&"trusted-publishing-checklist"));
	assert_eq!(report.next_steps.len(), 3);

	let text = render_migration_audit_report(&report, OutputFormat::Text);
	assert!(text.contains("migration audit: migration-needed"));
	assert!(text.contains("GitHub Actions workflow references cargo-release"));
	let json = render_migration_audit_report(&report, OutputFormat::Json);
	assert!(json.contains("\"status\": \"migration-needed\""));
}

#[test]
fn audit_migration_reports_ready_for_empty_workspace() {
	let tempdir = tempfile::tempdir().unwrap_or_else(|error| panic!("tempdir: {error}"));

	let report =
		audit_migration(tempdir.path()).unwrap_or_else(|error| panic!("audit migration: {error}"));

	assert_eq!(report.status, MigrationAuditStatus::Ready);
	assert!(report.signals.is_empty());
	assert!(report.recommendations.iter().any(|recommendation| {
		recommendation.id == "generate-config"
			&& recommendation.title == "Generate monochange configuration"
	}));
	assert!(
		report
			.recommendations
			.iter()
			.any(|recommendation| { recommendation.id == "trusted-publishing-checklist" })
	);

	let text = render_migration_audit_report(&report, OutputFormat::Markdown);
	assert!(text.contains("migration audit: ready"));
	assert!(text.contains("- none detected"));
	assert!(text.contains("Generate monochange configuration"));
}

#[test]
fn audit_migration_ignores_non_migration_signals_for_status() {
	let tempdir = tempfile::tempdir().unwrap_or_else(|error| panic!("tempdir: {error}"));
	let root = tempdir.path();
	write_file(root, "monochange.toml", "[workspace]\n");
	write_file(root, "CHANGELOG.md", "# Changelog\n");

	let report = audit_migration(root).unwrap_or_else(|error| panic!("audit migration: {error}"));

	assert_eq!(report.status, MigrationAuditStatus::Ready);
	assert_eq!(migration_signals(&report.signals).count(), 0);
	assert!(
		report
			.recommendations
			.iter()
			.any(|recommendation| { recommendation.id == "audit-changelogs" })
	);
	assert!(
		!report
			.recommendations
			.iter()
			.any(|recommendation| { recommendation.id == "trusted-publishing-checklist" })
	);
}