mirror of
https://github.com/Darkrock-Studios/hammer-editor.git
synced 2026-10-19 01:31:24 +00:00
Add an auto-publish option to the release dialog
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
Some checks failed
Build CI / build (push) Has been cancelled
Build CI / static-analysis (push) Has been cancelled
Build CI / android-instrumented-tests (push) Has been cancelled
Build CI / iOS compile & test (push) Has been cancelled
Build CI / iOS UI tests (push) Has been cancelled
PublishInternal / publish-google-play (push) Has been cancelled
The dialog gets an "Auto-publish the GitHub release" checkbox, off by default. When checked, prepareForRelease appends an `Auto-Publish: true` trailer to the tag message; set-release-body strips it from the release body and, once every build job passes, marks the release as the latest production release, which starts the store publish workflow. The tag name could not carry the flag: a `+` suffix there reads as a partial release and would gate off every store. Publishing uses RELEASE_PAT rather than GITHUB_TOKEN, because GitHub does not start workflow runs from events triggered by the default token. The dialog grew a section, so the frame is taller to keep the changelog editor its original height.
This commit is contained in:
parent
9fd796c342
commit
405c3b0ae6
5 changed files with 134 additions and 2 deletions
26
.github/workflows/prepare-release.yml
vendored
26
.github/workflows/prepare-release.yml
vendored
|
|
@ -595,6 +595,16 @@ jobs:
|
|||
run: |
|
||||
TAG_NAME=${GITHUB_REF#refs/tags/}
|
||||
TAG_MESSAGE=$(git tag -n90 --format='%(contents)' "$TAG_NAME" )
|
||||
|
||||
# prepareForRelease appends this trailer when the release dialog's
|
||||
# auto-publish box is checked. Strip it so it stays out of the body.
|
||||
AUTO_PUBLISH=false
|
||||
if printf '%s\n' "$TAG_MESSAGE" | grep -qxF 'Auto-Publish: true'; then
|
||||
AUTO_PUBLISH=true
|
||||
fi
|
||||
TAG_MESSAGE=$(printf '%s\n' "$TAG_MESSAGE" | grep -vxF 'Auto-Publish: true' || true)
|
||||
echo "auto_publish=$AUTO_PUBLISH" >> $GITHUB_ENV
|
||||
echo "Auto-publish: $AUTO_PUBLISH"
|
||||
|
||||
echo "Tag: $TAG_NAME"
|
||||
echo -e "Tag message:\n$TAG_MESSAGE"
|
||||
|
|
@ -612,3 +622,19 @@ jobs:
|
|||
prerelease: true
|
||||
allowUpdates: true
|
||||
body: ${{ env.tag_message }}
|
||||
|
||||
# Flipping the draft fires `release: published`, which starts
|
||||
# publish-release.yml. The token must not be GITHUB_TOKEN: GitHub suppresses
|
||||
# workflow runs triggered by it, so no store would ever publish.
|
||||
- name: Publish release
|
||||
if: env.auto_publish == 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.RELEASE_PAT }}
|
||||
REPO: ${{ github.repository }}
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [ -z "$GH_TOKEN" ]; then
|
||||
echo "::error::Auto-publish was requested but the RELEASE_PAT secret is not set."
|
||||
exit 1
|
||||
fi
|
||||
gh release edit "$TAG_NAME" --repo "$REPO" --draft=false --prerelease=false --latest
|
||||
|
|
|
|||
|
|
@ -334,7 +334,7 @@ tasks.register("prepareForRelease") {
|
|||
// Tag the merge commit explicitly; the main tree stays on develop.
|
||||
val tagMessageFile = File(project.rootDir, "build/release-tag-message.txt")
|
||||
tagMessageFile.parentFile.mkdirs()
|
||||
tagMessageFile.writeText(releaseInfo.changeLog)
|
||||
tagMessageFile.writeText(releaseInfo.tagMessage)
|
||||
git("tag", "-a", releaseInfo.tag, "-F", tagMessageFile.absolutePath, "release")
|
||||
|
||||
// Push the branches and only this release's tag. Pushing --tags would try
|
||||
|
|
|
|||
|
|
@ -80,15 +80,20 @@ fun writeBakedChangelog(entry: String, bakedFile: File) {
|
|||
println("Baked changelog written to ${bakedFile.path}")
|
||||
}
|
||||
|
||||
/** Tag-message trailer that tells CI to publish the release instead of leaving it a draft. */
|
||||
const val AUTO_PUBLISH_TRAILER = "Auto-Publish: true"
|
||||
|
||||
/**
|
||||
* @param changeLog The full release notes: CHANGELOG.md and the GitHub release.
|
||||
* @param storeChangeLog The app-only subset every store listing carries.
|
||||
* @param autoPublish Publish the GitHub release once every CI build job passes.
|
||||
*/
|
||||
data class ReleaseInfo(
|
||||
val semVar: SemVar,
|
||||
val changeLog: String,
|
||||
val storeChangeLog: String,
|
||||
val platforms: Set<Platform>,
|
||||
val autoPublish: Boolean = false,
|
||||
) {
|
||||
init {
|
||||
// Fail fast at construction so we never get halfway through prepareForRelease
|
||||
|
|
@ -99,6 +104,11 @@ data class ReleaseInfo(
|
|||
|
||||
/** The git tag for this release: `vX.Y.Z` for full, `vX.Y.Z+token+token` for partial. */
|
||||
val tag: String get() = "v$semVar${tagSuffix(platforms)}"
|
||||
|
||||
// The trailer is the only channel that reaches CI per-release: the tag name already
|
||||
// encodes platform scope, and a `+` suffix there would read as a partial release.
|
||||
/** The annotated-tag message: the notes, plus the auto-publish trailer when asked for. */
|
||||
val tagMessage: String get() = if (autoPublish) "$changeLog\n\n$AUTO_PUBLISH_TRAILER" else changeLog
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -151,7 +161,8 @@ fun configureRelease(currentSemVarStr: String, lastReleaseChangelog: String? = n
|
|||
SwingUtilities.invokeAndWait {
|
||||
val frame = JFrame()
|
||||
frame.defaultCloseOperation = JFrame.DISPOSE_ON_CLOSE
|
||||
frame.setSize(620, 900)
|
||||
// The fixed sections take the height they need; this leaves the changelog editor ~250px.
|
||||
frame.setSize(620, 1080)
|
||||
|
||||
fun refreshTitle() {
|
||||
frame.title = "Prepare Release — $curSemVar → $newSemVar"
|
||||
|
|
@ -374,6 +385,18 @@ fun configureRelease(currentSemVarStr: String, lastReleaseChangelog: String? = n
|
|||
// ============= Section: Will push tag =============
|
||||
val tagSection = section("Will push tag").apply { add(tagLabel) }
|
||||
|
||||
// ============= Section: After the build =============
|
||||
val autoPublishBox = JCheckBox("Auto-publish the GitHub release").apply {
|
||||
alignmentX = Component.LEFT_ALIGNMENT
|
||||
}
|
||||
val autoPublishSection = section("After the build").apply {
|
||||
add(autoPublishBox)
|
||||
add(
|
||||
tabHint("Off leaves a draft. Publishing starts the store workflow.")
|
||||
.apply { alignmentX = Component.LEFT_ALIGNMENT },
|
||||
)
|
||||
}
|
||||
|
||||
// ============= Section: Changelog =============
|
||||
val changelogSection = section("Changelog")
|
||||
|
||||
|
|
@ -467,6 +490,7 @@ fun configureRelease(currentSemVarStr: String, lastReleaseChangelog: String? = n
|
|||
changeLog = changeLog.text,
|
||||
storeChangeLog = storeNotes.text,
|
||||
platforms = currentPlatforms(),
|
||||
autoPublish = autoPublishBox.isSelected,
|
||||
)
|
||||
frame.dispose()
|
||||
}
|
||||
|
|
@ -486,6 +510,8 @@ fun configureRelease(currentSemVarStr: String, lastReleaseChangelog: String? = n
|
|||
add(Box.createRigidArea(Dimension(0, 8)))
|
||||
add(tagSection)
|
||||
add(Box.createRigidArea(Dimension(0, 8)))
|
||||
add(autoPublishSection)
|
||||
add(Box.createRigidArea(Dimension(0, 8)))
|
||||
}
|
||||
val root = JPanel(BorderLayout()).apply {
|
||||
border = BorderFactory.createEmptyBorder(16, 20, 16, 20)
|
||||
|
|
|
|||
65
buildSrc/src/test/java/ReleaseInfoTest.kt
Normal file
65
buildSrc/src/test/java/ReleaseInfoTest.kt
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
package com.darkrockstudios.build
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ReleaseInfoTest {
|
||||
|
||||
private fun releaseInfo(autoPublish: Boolean) = ReleaseInfo(
|
||||
semVar = SemVar(1, 2, 3),
|
||||
changeLog = "Added a thing\n- detail one",
|
||||
storeChangeLog = "Added a thing",
|
||||
platforms = Platform.ALL,
|
||||
autoPublish = autoPublish,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `Auto-publish is off by default`() {
|
||||
assertFalse(
|
||||
ReleaseInfo(
|
||||
semVar = SemVar(1, 2, 3),
|
||||
changeLog = "notes",
|
||||
storeChangeLog = "notes",
|
||||
platforms = Platform.ALL,
|
||||
).autoPublish
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Tag message is the changelog alone when auto-publish is off`() {
|
||||
assertEquals("Added a thing\n- detail one", releaseInfo(autoPublish = false).tagMessage)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Tag message appends the trailer as its own last line when auto-publish is on`() {
|
||||
val lines = releaseInfo(autoPublish = true).tagMessage.lines()
|
||||
assertEquals(AUTO_PUBLISH_TRAILER, lines.last())
|
||||
// The blank line keeps the trailer out of the last changelog paragraph, so
|
||||
// set-release-body strips it without leaving a dangling line in the body.
|
||||
assertEquals("", lines[lines.size - 2])
|
||||
}
|
||||
|
||||
// set-release-body greps for this exact whole line; a reword here silently stops
|
||||
// CI from publishing.
|
||||
@Test
|
||||
fun `Trailer wire format is stable`() {
|
||||
assertEquals("Auto-Publish: true", AUTO_PUBLISH_TRAILER)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Dropping the trailer restores the plain changelog`() {
|
||||
val stripped = releaseInfo(autoPublish = true).tagMessage
|
||||
.lines()
|
||||
.filterNot { it == AUTO_PUBLISH_TRAILER }
|
||||
.joinToString("\n")
|
||||
.trimEnd()
|
||||
assertEquals(releaseInfo(autoPublish = false).tagMessage, stripped)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Trailer never leaks into a release that did not ask for it`() {
|
||||
assertTrue(AUTO_PUBLISH_TRAILER !in releaseInfo(autoPublish = false).tagMessage)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,8 @@
|
|||
- Make sure your local repository is in a clean state, nothing outstanding
|
||||
- Change branch to `develop`
|
||||
- When `develop` is ready to release, run: `./gradlew prepareForRelease`
|
||||
- Leave **Auto-publish the GitHub release** unchecked to publish by hand (see
|
||||
[Auto-publishing](#auto-publishing))
|
||||
- This will prepare your repo by doing the following:
|
||||
- Increment app version `app` in `libs.versions.toml`
|
||||
- Add new changelog in `fastlane\metadata\android\en-US\changelogs` called `n.txt` where `n` is
|
||||
|
|
@ -24,6 +26,19 @@
|
|||
to [hammer.ink](https://hammer.ink), and notify the **Discord** channel of a new release
|
||||
- All done!
|
||||
|
||||
## Auto-publishing
|
||||
|
||||
The release dialog's **Auto-publish the GitHub release** checkbox (off by default) does the
|
||||
manual publish steps above for you. When it is checked `prepareForRelease` appends an
|
||||
`Auto-Publish: true` trailer to the tag message; once every build job passes, `set-release-body`
|
||||
strips the trailer from the release body and marks the release as the latest production release,
|
||||
firing the same `publish` action.
|
||||
|
||||
This needs a `RELEASE_PAT` repository secret: a fine-grained PAT scoped to this repo with
|
||||
**Contents: read and write**. GitHub does not start workflow runs from events triggered by the
|
||||
default `GITHUB_TOKEN`, so publishing with that token would leave every store untouched. The step
|
||||
fails with a clear error when the secret is missing.
|
||||
|
||||
## Partial (single-store) releases
|
||||
|
||||
Use this when one store needs a hotfix and you don't want to ship the whole matrix.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue