openclaw/scripts/lib/release-notes-compaction.mjs
Hannes Rudolph 2227743f74
refactor: split release changelogs and synchronize docs mirrors (#145464)
* refactor: split release changelogs and synchronize docs mirrors

* fix: complete split changelog instructions and validation wiring

* fix: complete release changelog mirror integration

Regenerate existing docs mirrors within the docs-agent publication boundary, preserve one HTML release heading, and package links for oversized mirrors without changing frozen records. Update release publisher and test-routing fixtures for the shared changelog resolver.

* test: align docs agent Git ownership fixtures

Keep failure injection aligned with staged-index validation and mirror staging. Preserve native Git producer exit codes and verify both cached-index producers without weakening process-drain assertions.

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-09-11 21:19:18 -07:00

64 lines
2.3 KiB
JavaScript

// Shared plain-JavaScript rendering also runs in package preflight before dependency setup.
export const OPENCLAW_RELEASE_TAG_PATTERN =
/^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(?:-(?:(?:alpha|beta)\.[1-9][0-9]*|[1-9][0-9]*))?$/u;
const CONTRIBUTION_RECORD_HEADING = "### Complete contribution record";
export function validateReleaseNotesRepository(repository) {
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/u.test(repository)) {
throw new Error(`invalid GitHub repository: ${repository}`);
}
}
export function validateReleaseNotesTag(tag) {
if (!OPENCLAW_RELEASE_TAG_PATTERN.test(tag)) {
throw new Error(`invalid release tag: ${tag}`);
}
}
function tagPinnedContributionRecordUrl(repository, tag, recordPath) {
validateReleaseNotesRepository(repository);
validateReleaseNotesTag(tag);
if (!/^CHANGELOG(?:\.md|\/records\/[A-Za-z0-9.-]+\.md)$/u.test(recordPath)) {
throw new Error(`invalid contribution record path: ${recordPath}`);
}
return `https://github.com/${repository}/blob/${tag}/${recordPath}#complete-contribution-record`;
}
function headingIndexOutsideFences(markdown, heading) {
let offset = 0;
let fence;
for (const segment of markdown.split(/(?<=\n)/u)) {
const line = segment.replace(/\n$/u, "");
const fenceMatch = line.match(/^\s*(?<marker>`{3,}|~{3,})/u);
if (fenceMatch?.groups?.marker) {
const marker = fenceMatch.groups.marker;
if (!fence) {
fence = marker;
} else if (marker.charAt(0) === fence.charAt(0) && marker.length >= fence.length) {
fence = undefined;
}
} else if (!fence && line === heading) {
return offset;
}
offset += segment.length;
}
return -1;
}
export function compactReleaseNotes(section, repository, tag, recordPath = "CHANGELOG.md") {
const recordIndex = headingIndexOutsideFences(section, CONTRIBUTION_RECORD_HEADING);
if (recordIndex < 0) {
return null;
}
const editorialNotes = section.slice(0, recordIndex).trimEnd();
const contributionRecordUrl = tagPinnedContributionRecordUrl(repository, tag, recordPath);
const body = [
editorialNotes,
"",
CONTRIBUTION_RECORD_HEADING,
"",
`The full contribution record is available in the tag-pinned [CHANGELOG.md](${contributionRecordUrl}).`,
].join("\n");
return { body, editorialNotes };
}