From 05f1a3287e6157d27b73ffdd955ed55f5370bb61 Mon Sep 17 00:00:00 2001 From: vineyardbovines Date: Thu, 3 Sep 2026 10:48:07 -0400 Subject: [PATCH] clrf cleanup --- docs/release-model.md | 21 ++++++--------------- scripts/release/model.mjs | 24 ++++++++++++++++++------ 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/docs/release-model.md b/docs/release-model.md index 4b7207733d..b9fbe7787f 100644 --- a/docs/release-model.md +++ b/docs/release-model.md @@ -1,14 +1,10 @@ # Release model -Each app release gets one document that keeps its version, build information, -and public changelog together in one place. We are building this new release -model alongside the existing production workflows so we can try it safely -before using it for a real release. +Each app release gets one document that keeps its version, build information, and public changelog together in one place. ## Identity -The workflow accepts one version in strict `x.y.z` format and derives all other -identifiers from it: +The workflow accepts one version in strict `x.y.z` format and derives all other identifiers from it: | Resource | Format | | --- | --- | @@ -22,8 +18,7 @@ Callers must not supply these derived identifiers independently. ## Prepared state -The preparation workflow creates the document before freezing the native -candidate. At this stage, only `releaseVersion` is required: +The preparation workflow creates the document before freezing the native candidate. At this stage, only `releaseVersion` is required: ```md --- @@ -43,8 +38,7 @@ releaseVersion: 1.131.1 ## Final state -After both native builds succeed, the workflow records the frozen source and -artifact-derived build numbers. A finalized document requires every field: +After both native builds succeed, the workflow records the frozen source and artifact-derived build numbers. A finalized document requires every field: ```yaml releaseVersion: 1.131.1 @@ -54,9 +48,6 @@ iosBuildNumber: 1662 androidVersionCode: 1110 ``` -`sourceTag` must equal `releaseVersion`, `sourceSha` must be a full Git object -ID, and both build numbers must be positive integers. +`sourceTag` must equal `releaseVersion`, `sourceSha` must be a full Git object ID, and both build numbers must be positive integers. -Each successful OTA adds exactly one contiguous section (`OTA 1`, `OTA 2`, and -so on) inside the public changelog delimiters. GitHub Release text is extracted -only from those delimiters; operational frontmatter is never published. +Each successful OTA adds exactly one contiguous section (`OTA 1`, `OTA 2`, and so on) inside the public changelog delimiters. GitHub Release text is extracted only from those delimiters; operational frontmatter is never published. diff --git a/scripts/release/model.mjs b/scripts/release/model.mjs index 7ec176da06..b7d2c931e2 100644 --- a/scripts/release/model.mjs +++ b/scripts/release/model.mjs @@ -30,6 +30,10 @@ function fail(message) { throw new ReleaseError(message) } +function normalizeLineEndings(markdown) { + return markdown.replace(/\r\n?/g, '\n') +} + export function assertReleaseVersion(version) { if (typeof version !== 'string' || !VERSION_PATTERN.test(version)) { fail(`Release version must use strict x.y.z format; found '${version}'.`) @@ -49,7 +53,7 @@ export function deriveReleaseIdentity(version) { } function parseFrontmatter(markdown) { - const normalized = markdown.replace(/\r\n/g, '\n') + const normalized = normalizeLineEndings(markdown) if (!normalized.startsWith('---\n')) { fail('Release file must start with YAML frontmatter.') } @@ -249,7 +253,8 @@ export function extractPublicChangelog(markdown) { } export function appendOtaChangelog(markdown, sequence, changelog) { - const parsed = parseReleaseDocument(markdown) + const normalized = normalizeLineEndings(markdown) + const parsed = parseReleaseDocument(normalized) const expectedSequence = parsed.sections.length if (!Number.isSafeInteger(sequence) || sequence !== expectedSequence) { fail(`Next OTA sequence must be ${expectedSequence}; found '${sequence}'.`) @@ -258,22 +263,29 @@ export function appendOtaChangelog(markdown, sequence, changelog) { if (!content) fail('OTA changelog cannot be empty.') const delimiter = `\n\n${PUBLIC_CHANGELOG_END}` + if (!normalized.includes(delimiter)) { + fail('Could not find the public changelog end delimiter to append the OTA.') + } const replacement = `\n\n## OTA ${sequence}\n\n${content}${delimiter}` - const updated = markdown.replace(delimiter, replacement) + const updated = normalized.replace(delimiter, replacement) parseReleaseDocument(updated) return updated } export function finalizeReleaseDocument(markdown, finalMetadata) { - const parsed = parseReleaseDocument(markdown) + const normalized = normalizeLineEndings(markdown) + const parsed = parseReleaseDocument(normalized) const metadata = {...parsed.metadata, ...finalMetadata} validateMetadata(metadata, 'final') const frontmatter = FRONTMATTER_KEYS.map( key => `${key}: ${metadata[key]}`, ).join('\n') - const updated = markdown.replace( - /^---\n[\s\S]*?\n---\n/, + const frontmatterPattern = /^---\n[\s\S]*?\n---\n/ + if (!frontmatterPattern.test(normalized)) + fail('Could not replace the release frontmatter.') + const updated = normalized.replace( + frontmatterPattern, `---\n${frontmatter}\n---\n`, ) parseReleaseDocument(updated, {stage: 'final'})