import { a as loadAnvilManifest, y as resolveStateRoot } from "./manifest-CslkSMd8.mjs";
import path from "node:path";
import process from "node:process";
import { mkdir, readFile, readdir, rm, stat, writeFile } from "node:fs/promises";
import { execa } from "execa";
//#region src/lib/release.ts
const packageJsonGlobs = ["@packages/*/package.json", "@catalog/blocks/*/package.json"];
const repoPattern = /^r(\d{4}-\d{2}-\d{2})\.(\d+)$/;
function resolveReleasePaths(cwd) {
	const loadedManifest = loadAnvilManifest(cwd);
	if (!loadedManifest) throw new Error("No anvil.yaml found.");
	const stateRoot = resolveStateRoot(loadedManifest);
	return {
		activeSummaryPath: path.join(stateRoot, "releases", "active.md"),
		changelogPath: path.join(stateRoot, "releases", "changelog.md"),
		globalPath: path.join(stateRoot, "releases", "global.md"),
		historyDir: path.join(stateRoot, "releases", "history"),
		releasesDir: path.join(stateRoot, "releases"),
		stateRoot
	};
}
async function readOptionalFile(filePath) {
	try {
		return (await readFile(filePath, "utf8")).trim();
	} catch {
		return "";
	}
}
function parseReleaseTitle(source) {
	const match = source.match(/^#\s+(\d{4}-\d{2}-\d{2}\.\d+)$/m);
	if (!match) throw new Error("Could not resolve release title from `@state/releases/active.md`.");
	return match[1];
}
function stripReleaseTitleHeading(source) {
	return source.replace(/^#\s+\d{4}-\d{2}-\d{2}\.\d+\n+/, "").trim();
}
function extractReleaseHighlights(source) {
	return source.match(/^## Highlights\s*([\s\S]*?)\n## Changelog$/m)?.[1]?.trim() ?? "";
}
function releaseSummaryIncludesPackages(source) {
	const changelog = source.match(/^## Changelog\s*([\s\S]*)$/m)?.[1] ?? source;
	return /^###\s+(?!global\b)/im.test(changelog);
}
async function isGitWorkingTreeClean(cwd) {
	return (await execa("git", ["status", "--porcelain"], { cwd })).stdout.trim() === "";
}
async function getChangedPackages(cwd, diffBase = "HEAD") {
	const packageFiles = (await execa("git", [
		"diff",
		"--name-only",
		diffBase,
		"--",
		...packageJsonGlobs
	], { cwd })).stdout.split("\n").map((line) => line.trim()).filter(Boolean);
	const packageDirs = [...new Set(packageFiles.map((file) => path.dirname(file)))].sort((left, right) => left.localeCompare(right));
	const changedPackages = [];
	for (const packageDir of packageDirs) {
		const packageJson = JSON.parse(await readFile(path.join(cwd, packageDir, "package.json"), "utf8"));
		if (!packageJson.name || !packageJson.version) continue;
		changedPackages.push({
			dir: packageDir,
			name: packageJson.name,
			private: packageJson.private === true,
			shortName: packageJson.name.split("/").at(-1) ?? packageJson.name,
			version: packageJson.version
		});
	}
	return changedPackages.sort((left, right) => left.shortName.localeCompare(right.shortName));
}
async function getExistingRepoTags(cwd) {
	return (await execa("git", [
		"tag",
		"--list",
		"r*"
	], { cwd })).stdout.split("\n").map((line) => line.trim()).filter(Boolean).map((tag) => {
		const match = tag.match(repoPattern);
		if (!match) return null;
		return {
			raw: tag,
			date: match[1],
			sequence: Number(match[2])
		};
	}).filter((tag) => tag !== null).sort((left, right) => left.date === right.date ? left.sequence - right.sequence : left.date.localeCompare(right.date));
}
async function resolveReleaseMarker(cwd) {
	const existingTags = await getExistingRepoTags(cwd);
	const existingHeadTag = (await execa("git", [
		"tag",
		"--points-at",
		"HEAD"
	], { cwd })).stdout.split("\n").map((line) => line.trim()).find((tag) => repoPattern.test(tag));
	if (existingHeadTag) {
		const currentIndex = existingTags.findIndex((tag) => tag.raw === existingHeadTag);
		return {
			releaseTitle: existingHeadTag.slice(1),
			releaseTag: existingHeadTag,
			previousTag: currentIndex > 0 ? existingTags[currentIndex - 1].raw : null
		};
	}
	const releaseDate = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
	const todaysTags = existingTags.filter((tag) => tag.date === releaseDate);
	const releaseTitle = `${releaseDate}.${todaysTags.length > 0 ? Math.max(...todaysTags.map((tag) => tag.sequence)) + 1 : 1}`;
	return {
		releaseTitle,
		releaseTag: `r${releaseTitle}`,
		previousTag: existingTags.length > 0 ? existingTags.at(-1)?.raw ?? null : null
	};
}
function buildCompareLink(previousTag, releaseTag) {
	if (!previousTag) return null;
	return `https://github.com/${process.env.GITHUB_REPOSITORY ?? "prata-ma/anvil"}/compare/${previousTag}...${releaseTag}`;
}
async function extractReleaseNotes(cwd, packageDir, version) {
	const preferredChangelogPath = path.join(cwd, packageDir, "CHANGELOG.md");
	const changelog = await readFile(preferredChangelogPath, "utf8");
	const headerPattern = new RegExp(`^##\\s+${version.replace(/\./g, "\\.")}\\b.*$`, "m");
	const headerMatch = changelog.match(headerPattern);
	if (!headerMatch || headerMatch.index === void 0) throw new Error(`Could not find version ${version} in ${preferredChangelogPath}`);
	const sectionStart = headerMatch.index;
	const nextHeaderIndex = changelog.slice(sectionStart + headerMatch[0].length).search(/^##\s+/m);
	const sectionEnd = nextHeaderIndex === -1 ? changelog.length : sectionStart + headerMatch[0].length + nextHeaderIndex;
	return changelog.slice(sectionStart, sectionEnd).trim();
}
function parseReleaseNotes(section) {
	const [, ...rest] = section.split("\n");
	const body = rest.join("\n").replace(/^\n+/, "").trim();
	const paragraphs = [];
	const bullets = [];
	const counts = /* @__PURE__ */ new Map();
	let paragraphLines = [];
	let currentBullet = [];
	let currentBucket = null;
	function flushParagraph() {
		const paragraph = paragraphLines.join("\n").trim();
		if (paragraph) paragraphs.push(paragraph);
		paragraphLines = [];
	}
	function flushBullet() {
		const bullet = currentBullet.join("\n").trimEnd();
		if (bullet) bullets.push(bullet);
		currentBullet = [];
	}
	for (const line of body.split("\n")) {
		const headingMatch = line.match(/^###\s+(Major|Minor|Patch) Changes$/);
		if (headingMatch) {
			flushParagraph();
			flushBullet();
			currentBucket = headingMatch[1].toLowerCase();
			continue;
		}
		if (line.startsWith("- ")) {
			flushParagraph();
			flushBullet();
			currentBullet = [line];
			if (currentBucket) counts.set(currentBucket, (counts.get(currentBucket) ?? 0) + 1);
			continue;
		}
		if (currentBullet.length > 0) {
			if (line === "" || line.startsWith("  ")) {
				currentBullet.push(line);
				continue;
			}
			flushBullet();
		}
		if (line.trim() === "") {
			flushParagraph();
			continue;
		}
		paragraphLines.push(line);
	}
	flushParagraph();
	flushBullet();
	let summary = paragraphs.join("\n\n").trim();
	if (!summary && counts.size > 0) summary = `This release includes ${joinPhrases([...counts.entries()].map(([kind, count]) => `${count} ${kind} change${count === 1 ? "" : "s"}`))}.`;
	return {
		bullets,
		summary
	};
}
function joinPhrases(parts) {
	if (parts.length === 0) return "";
	if (parts.length === 1) return parts[0];
	if (parts.length === 2) return `${parts[0]} and ${parts[1]}`;
	return `${parts.slice(0, -1).join(", ")}, and ${parts.at(-1)}`;
}
async function buildReleaseChangelog(cwd, packages) {
	const packageSections = [];
	for (const pkg of packages) {
		const { bullets } = parseReleaseNotes(await extractReleaseNotes(cwd, pkg.dir, pkg.version));
		const lines = [pkg.private ? `### ${pkg.shortName} v${pkg.version}` : `### ${pkg.shortName} [v${pkg.version}](https://npmx.dev/package/${pkg.name}/v/${pkg.version})`];
		if (bullets.length > 0) lines.push("", ...bullets);
		packageSections.push(lines.join("\n").trim());
	}
	return packageSections.join("\n\n").trim();
}
function buildActiveReleaseSummary(marker, highlights) {
	const compareLink = buildCompareLink(marker.previousTag, marker.releaseTag);
	const lines = [
		`# ${marker.releaseTitle}`,
		"",
		"## Highlights",
		""
	];
	if (highlights.trim()) lines.push(highlights.trim(), "");
	else lines.push("_Add release highlights before publish._", "");
	lines.push("## Changelog", "");
	if (compareLink) lines.push(`[Compare changes](${compareLink})`, "");
	lines.push("_Compose final changelog from @state/releases/global.md and @state/releases/changelog.md before publish._");
	return lines.join("\n").trim();
}
async function writeActiveReleaseSummary(cwd, summary) {
	const releasePaths = resolveReleasePaths(cwd);
	await mkdir(path.dirname(releasePaths.activeSummaryPath), { recursive: true });
	await writeFile(releasePaths.activeSummaryPath, `${summary.trim()}\n`, "utf8");
}
async function writeReleaseChangelog(cwd, changelog) {
	const releasePaths = resolveReleasePaths(cwd);
	await mkdir(path.dirname(releasePaths.changelogPath), { recursive: true });
	await writeFile(releasePaths.changelogPath, changelog.trim() ? `${changelog.trim()}\n` : "", "utf8");
}
async function readActiveReleaseSummary(cwd) {
	return readOptionalFile(resolveReleasePaths(cwd).activeSummaryPath);
}
async function deleteReleaseDraftArtifacts(cwd) {
	const releasePaths = resolveReleasePaths(cwd);
	await rm(releasePaths.activeSummaryPath, { force: true });
	await rm(releasePaths.globalPath, { force: true });
	await rm(releasePaths.changelogPath, { force: true });
}
async function prepareReleaseSummary(cwd) {
	const packages = await getChangedPackages(cwd, "HEAD");
	const marker = await resolveReleaseMarker(cwd);
	const highlights = extractReleaseHighlights(await readActiveReleaseSummary(cwd));
	const changelog = await buildReleaseChangelog(cwd, packages);
	const summary = buildActiveReleaseSummary(marker, highlights);
	await writeReleaseChangelog(cwd, changelog);
	await writeActiveReleaseSummary(cwd, summary);
	return {
		marker,
		packageCount: packages.length
	};
}
async function syncBlockPackageVersions(cwd) {
	const blocksRoot = path.join(cwd, "@catalog", "blocks");
	let updated = 0;
	for (const blockDir of await listBlockDirectories(blocksRoot)) {
		const packagePath = path.join(blockDir, "package.json");
		const blockPath = path.join(blockDir, "block.yaml");
		try {
			await stat(packagePath);
			await stat(blockPath);
		} catch {
			continue;
		}
		const packageJson = JSON.parse(await readFile(packagePath, "utf8"));
		if (!packageJson.version) continue;
		const blockYaml = await readFile(blockPath, "utf8");
		const nextBlockYaml = replaceYamlScalar(blockYaml, "version", packageJson.version);
		if (nextBlockYaml === blockYaml) continue;
		await writeFile(blockPath, nextBlockYaml, "utf8");
		updated += 1;
	}
	return updated;
}
async function finalizeRelease(cwd) {
	const notes = await readActiveReleaseSummary(cwd);
	if (!notes) throw new Error("No active repo release summary found at `@state/releases/active.md`.");
	const githubReleaseNotes = stripReleaseTitleHeading(notes);
	const releaseTitle = parseReleaseTitle(notes);
	const marker = {
		releaseTitle,
		releaseTag: `r${releaseTitle}`,
		previousTag: null
	};
	const notesFile = path.join(cwd, ".tmp", `repo-release-${marker.releaseTag}.md`);
	await mkdir(path.dirname(notesFile), { recursive: true });
	await writeFile(notesFile, `${githubReleaseNotes}\n`, "utf8");
	if ((await execa("git", [
		"tag",
		"--list",
		marker.releaseTag
	], {
		cwd,
		reject: false
	})).stdout.trim() === "") {
		await execa("git", [
			"tag",
			"-a",
			marker.releaseTag,
			"-m",
			marker.releaseTag
		], {
			cwd,
			stdio: "inherit"
		});
		await execa("git", [
			"push",
			"origin",
			`refs/tags/${marker.releaseTag}`
		], {
			cwd,
			stdio: "inherit"
		});
	}
	await execa("gh", (await execa("gh", [
		"release",
		"view",
		marker.releaseTag
	], {
		cwd,
		reject: false
	})).exitCode === 0 ? [
		"release",
		"edit",
		marker.releaseTag,
		"--title",
		marker.releaseTitle,
		"--notes-file",
		notesFile
	] : [
		"release",
		"create",
		marker.releaseTag,
		"--title",
		marker.releaseTitle,
		"--notes-file",
		notesFile
	], {
		cwd,
		stdio: "inherit"
	});
	const releasePaths = resolveReleasePaths(cwd);
	await mkdir(releasePaths.historyDir, { recursive: true });
	await writeFile(path.join(releasePaths.historyDir, `${marker.releaseTag}.md`), `${notes}\n`, "utf8");
	await deleteReleaseDraftArtifacts(cwd);
	return marker;
}
async function listBlockDirectories(blocksRoot) {
	return (await readdir(blocksRoot, { withFileTypes: true }).catch(() => [])).filter((entry) => entry.isDirectory()).map((entry) => path.join(blocksRoot, entry.name));
}
function replaceYamlScalar(source, key, value) {
	const pattern = new RegExp(`^(${key}:\\s*).+$`, "m");
	return source.replace(pattern, `$1${value}`);
}
async function getReleaseStatus(cwd) {
	const activeSummary = await readActiveReleaseSummary(cwd);
	return {
		activeSummary: activeSummary.length > 0,
		activeTitle: activeSummary ? parseReleaseTitle(activeSummary) : null,
		changedPackages: await getChangedPackages(cwd, "HEAD"),
		cleanWorkingTree: await isGitWorkingTreeClean(cwd)
	};
}
//#endregion
export { readActiveReleaseSummary as a, prepareReleaseSummary as i, getReleaseStatus as n, releaseSummaryIncludesPackages as o, isGitWorkingTreeClean as r, syncBlockPackageVersions as s, finalizeRelease as t };

//# sourceMappingURL=release-C3xNWdlR.mjs.map