clrf cleanup

This commit is contained in:
vineyardbovines
2026-09-03 10:48:07 -04:00
parent b0a79ab2d0
commit 05f1a3287e
2 changed files with 24 additions and 21 deletions
+6 -15
View File
@@ -1,14 +1,10 @@
# Release model # Release model
Each app release gets one document that keeps its version, build information, Each app release gets one document that keeps its version, build information, and public changelog together in one place.
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.
## Identity ## Identity
The workflow accepts one version in strict `x.y.z` format and derives all other The workflow accepts one version in strict `x.y.z` format and derives all other identifiers from it:
identifiers from it:
| Resource | Format | | Resource | Format |
| --- | --- | | --- | --- |
@@ -22,8 +18,7 @@ Callers must not supply these derived identifiers independently.
## Prepared state ## Prepared state
The preparation workflow creates the document before freezing the native The preparation workflow creates the document before freezing the native candidate. At this stage, only `releaseVersion` is required:
candidate. At this stage, only `releaseVersion` is required:
```md ```md
--- ---
@@ -43,8 +38,7 @@ releaseVersion: 1.131.1
## Final state ## Final state
After both native builds succeed, the workflow records the frozen source and After both native builds succeed, the workflow records the frozen source and artifact-derived build numbers. A finalized document requires every field:
artifact-derived build numbers. A finalized document requires every field:
```yaml ```yaml
releaseVersion: 1.131.1 releaseVersion: 1.131.1
@@ -54,9 +48,6 @@ iosBuildNumber: 1662
androidVersionCode: 1110 androidVersionCode: 1110
``` ```
`sourceTag` must equal `releaseVersion`, `sourceSha` must be a full Git object `sourceTag` must equal `releaseVersion`, `sourceSha` must be a full Git object ID, and both build numbers must be positive integers.
ID, and both build numbers must be positive integers.
Each successful OTA adds exactly one contiguous section (`OTA 1`, `OTA 2`, and 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.
so on) inside the public changelog delimiters. GitHub Release text is extracted
only from those delimiters; operational frontmatter is never published.
+18 -6
View File
@@ -30,6 +30,10 @@ function fail(message) {
throw new ReleaseError(message) throw new ReleaseError(message)
} }
function normalizeLineEndings(markdown) {
return markdown.replace(/\r\n?/g, '\n')
}
export function assertReleaseVersion(version) { export function assertReleaseVersion(version) {
if (typeof version !== 'string' || !VERSION_PATTERN.test(version)) { if (typeof version !== 'string' || !VERSION_PATTERN.test(version)) {
fail(`Release version must use strict x.y.z format; found '${version}'.`) fail(`Release version must use strict x.y.z format; found '${version}'.`)
@@ -49,7 +53,7 @@ export function deriveReleaseIdentity(version) {
} }
function parseFrontmatter(markdown) { function parseFrontmatter(markdown) {
const normalized = markdown.replace(/\r\n/g, '\n') const normalized = normalizeLineEndings(markdown)
if (!normalized.startsWith('---\n')) { if (!normalized.startsWith('---\n')) {
fail('Release file must start with YAML frontmatter.') fail('Release file must start with YAML frontmatter.')
} }
@@ -249,7 +253,8 @@ export function extractPublicChangelog(markdown) {
} }
export function appendOtaChangelog(markdown, sequence, changelog) { export function appendOtaChangelog(markdown, sequence, changelog) {
const parsed = parseReleaseDocument(markdown) const normalized = normalizeLineEndings(markdown)
const parsed = parseReleaseDocument(normalized)
const expectedSequence = parsed.sections.length const expectedSequence = parsed.sections.length
if (!Number.isSafeInteger(sequence) || sequence !== expectedSequence) { if (!Number.isSafeInteger(sequence) || sequence !== expectedSequence) {
fail(`Next OTA sequence must be ${expectedSequence}; found '${sequence}'.`) 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.') if (!content) fail('OTA changelog cannot be empty.')
const delimiter = `\n\n${PUBLIC_CHANGELOG_END}` 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 replacement = `\n\n## OTA ${sequence}\n\n${content}${delimiter}`
const updated = markdown.replace(delimiter, replacement) const updated = normalized.replace(delimiter, replacement)
parseReleaseDocument(updated) parseReleaseDocument(updated)
return updated return updated
} }
export function finalizeReleaseDocument(markdown, finalMetadata) { export function finalizeReleaseDocument(markdown, finalMetadata) {
const parsed = parseReleaseDocument(markdown) const normalized = normalizeLineEndings(markdown)
const parsed = parseReleaseDocument(normalized)
const metadata = {...parsed.metadata, ...finalMetadata} const metadata = {...parsed.metadata, ...finalMetadata}
validateMetadata(metadata, 'final') validateMetadata(metadata, 'final')
const frontmatter = FRONTMATTER_KEYS.map( const frontmatter = FRONTMATTER_KEYS.map(
key => `${key}: ${metadata[key]}`, key => `${key}: ${metadata[key]}`,
).join('\n') ).join('\n')
const updated = markdown.replace( const frontmatterPattern = /^---\n[\s\S]*?\n---\n/
/^---\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`, `---\n${frontmatter}\n---\n`,
) )
parseReleaseDocument(updated, {stage: 'final'}) parseReleaseDocument(updated, {stage: 'final'})