ci: stage agent-managed releases from the release branch
Build drafts from explicit workspace versions, preserve dry runs, and document agent verification and publication. Reject existing release tags and published versions instead of publishing on every development push.
This commit is contained in:
1 parent
88b4c5b883
commit
7a98800699
4 files changed
+174
-22
No files matched your search
@@ -1,9 +1,9 @@
|
||||
# Release pipeline, after PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
#
|
||||
# Push a tag `v<version>` (matching [workspace.package] version in Cargo.toml) and this builds and
|
||||
# packages SolveCraft for every platform and publishes the GitHub release "SolveCraft v<version>"
|
||||
# with SHA256SUMS.txt, .github/release-notes.md and GitHub's generated "What's Changed". A
|
||||
# version with a suffix (0.2.0-rc.1) is a pre-release. Nothing is signed yet.
|
||||
# Push to `release` to build packages and create/update a DRAFT GitHub Release. The version
|
||||
# comes from [workspace.package] in Cargo.toml. An agent verifies the artifacts and publishes
|
||||
# the draft separately; see docs/releasing.md. Tags and pushes to main do not trigger releases.
|
||||
# A version with a suffix (0.2.0-rc.1) is a pre-release. Nothing is signed yet.
|
||||
#
|
||||
# Linux x86_64, aarch64 AppImage (+ .zsync), .deb, .rpm, .flatpak, .tar.gz packaging/linux
|
||||
# FreeBSD x86_64 .tar.gz (built in a FreeBSD VM) packaging/freebsd
|
||||
@@ -11,17 +11,16 @@
|
||||
# Windows x64, x86, arm64 .msi and a portable .zip; x64 also the plain .exe packaging/windows
|
||||
# Web a .zip of the static site packaging/web
|
||||
#
|
||||
# A dry run: Actions > Release > Run workflow (publish off) builds everything and keeps it as
|
||||
# the release-assets artifact instead of releasing.
|
||||
# A dry run: dispatch with draft=false (the default). Only the release branch may create drafts.
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ["v*"]
|
||||
branches: [release]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
publish:
|
||||
description: "Publish the release (off: a dry run; the files become the release-assets artifact)"
|
||||
draft:
|
||||
description: "Create/update a draft (release branch only; off: build artifacts only)"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
@@ -47,10 +46,16 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
- id: v
|
||||
name: Resolve version
|
||||
env:
|
||||
REQUEST_DRAFT: ${{ inputs.draft || false }}
|
||||
run: |
|
||||
. packaging/env.sh
|
||||
if [ "$GITHUB_REF_TYPE" = tag ] && [ "${GITHUB_REF_NAME#v}" != "$VERSION" ]; then
|
||||
echo "::error::tag $GITHUB_REF_NAME doesn't match the Cargo.toml version $VERSION"
|
||||
if ! printf '%s' "$VERSION" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-((0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*)(\.(0|[1-9][0-9]*|[0-9]*[A-Za-z-][0-9A-Za-z-]*))*))?$'; then
|
||||
echo "::error::Cargo.toml must contain a version like 1.2.3 or 1.2.3-rc.1"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$REQUEST_DRAFT" = true ] && [ "$GITHUB_REF" != refs/heads/release ]; then
|
||||
echo "::error::Draft releases must be built from the release branch"
|
||||
exit 1
|
||||
fi
|
||||
case "$VERSION" in *-*) pre=true ;; *) pre=false ;; esac
|
||||
@@ -295,7 +300,7 @@ jobs:
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
name: GitHub Release
|
||||
name: Draft GitHub Release or dry-run artifacts
|
||||
needs: [version, linux, flatpak, freebsd, macos, windows, web]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
@@ -316,27 +321,47 @@ jobs:
|
||||
run: |
|
||||
(cd artifacts && sha256sum -- * | tee SHA256SUMS.txt)
|
||||
sed "s/{version}/$VERSION/g" .github/release-notes.md >notes.md
|
||||
- name: Publish the release
|
||||
if: github.event_name == 'push' || inputs.publish
|
||||
- name: Create or update the draft release
|
||||
if: github.ref == 'refs/heads/release' && (github.event_name == 'push' || inputs.draft)
|
||||
working-directory: artifacts
|
||||
run: |
|
||||
tag="v$VERSION"
|
||||
pre=()
|
||||
if [ "$PRERELEASE" = true ]; then pre=(--prerelease); else pre=(--latest); fi
|
||||
if gh release view "$tag" >/dev/null 2>&1; then
|
||||
echo "::error::Release $tag already exists. Bump the version before tagging."
|
||||
# Refuse existing tags: a draft must point at this build, never an older tag.
|
||||
# matching-refs returns [] for an absent tag and fails on API/auth errors.
|
||||
existing_refs="$(gh api "repos/$GH_REPO/git/matching-refs/tags/$tag" \
|
||||
--jq '.[].ref')"
|
||||
if printf '%s\n' "$existing_refs" | grep -Fxq "refs/tags/$tag"; then
|
||||
echo "::error::Tag $tag already exists. Choose a new version; never move a release tag."
|
||||
exit 1
|
||||
fi
|
||||
gh release create "$tag" --title "SolveCraft v$VERSION" --verify-tag --notes-file ../notes.md --generate-notes "${pre[@]}" -- *
|
||||
pre=()
|
||||
if [ "$PRERELEASE" = true ]; then pre=(--prerelease); fi
|
||||
if draft="$(gh release view "$tag" --json isDraft --jq .isDraft 2>/dev/null)"; then
|
||||
if [ "$draft" != true ]; then
|
||||
echo "::error::Release $tag is already published. Bump the version before releasing."
|
||||
exit 1
|
||||
fi
|
||||
# Replace the complete draft asset set, including assets removed in this build.
|
||||
assets="$(gh release view "$tag" --json assets --jq '.assets[].name')"
|
||||
while IFS= read -r asset; do
|
||||
if [ -n "$asset" ]; then gh release delete-asset "$tag" "$asset" --yes; fi
|
||||
done <<<"$assets"
|
||||
gh release edit "$tag" --draft --title "SolveCraft v$VERSION" \
|
||||
--target "$GITHUB_SHA" --prerelease="$PRERELEASE"
|
||||
gh release upload "$tag" --clobber -- *
|
||||
else
|
||||
gh release create "$tag" --draft --title "SolveCraft v$VERSION" \
|
||||
--target "$GITHUB_SHA" --notes-file ../notes.md --generate-notes "${pre[@]}" -- *
|
||||
fi
|
||||
{
|
||||
echo "Published **SolveCraft v$VERSION** at \`${GITHUB_SHA::9}\`:"
|
||||
echo "Draft **SolveCraft v$VERSION** at \`${GITHUB_SHA::9}\`:"
|
||||
echo
|
||||
sed 's/^/ /' SHA256SUMS.txt
|
||||
} >>"$GITHUB_STEP_SUMMARY"
|
||||
- name: Keep the files (dry run)
|
||||
if: github.event_name == 'workflow_dispatch' && !inputs.publish
|
||||
if: github.event_name == 'workflow_dispatch' && !inputs.draft
|
||||
run: cp notes.md artifacts/release-notes.md
|
||||
- if: github.event_name == 'workflow_dispatch' && !inputs.publish
|
||||
- if: github.event_name == 'workflow_dispatch' && !inputs.draft
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: release-assets
|
||||
|
||||
@@ -71,6 +71,15 @@ People trust SolveCraft with their designs; a crash loses their work. **This out
|
||||
- **Never break wasm**: everything below the apps must build for `wasm32-unknown-unknown`
|
||||
(`cargo xtask wasm`, part of `cargo xtask ci`).
|
||||
|
||||
## Application releases
|
||||
Development pushes are not application releases. Follow `docs/releasing.md` when asked to
|
||||
prepare or ship a release: choose the version from the actual changes, update Cargo.toml and
|
||||
Cargo.lock, pass the quality gates, advance `release` without force-pushing, and verify the
|
||||
resulting draft and its artifacts. When shipping is authorized, publish that verified draft
|
||||
and confirm its tag and downstream deployment. Do not auto-release every push to `main`, infer
|
||||
version bumps solely from commit prefixes, overwrite published versions, or push release tags
|
||||
before the draft is verified.
|
||||
|
||||
## Running and looking at the app
|
||||
- `cargo run --release -p solvecraft -- --sample --control PORT` (sample design + control channel).
|
||||
Pick a free port. Without a display, run it under `Xvfb :99` with `DISPLAY=:99`.
|
||||
|
||||
@@ -71,6 +71,15 @@ People trust SolveCraft with their designs; a crash loses their work. **This out
|
||||
- **Never break wasm**: everything below the apps must build for `wasm32-unknown-unknown`
|
||||
(`cargo xtask wasm`, part of `cargo xtask ci`).
|
||||
|
||||
## Application releases
|
||||
Development pushes are not application releases. Follow `docs/releasing.md` when asked to
|
||||
prepare or ship a release: choose the version from the actual changes, update Cargo.toml and
|
||||
Cargo.lock, pass the quality gates, advance `release` without force-pushing, and verify the
|
||||
resulting draft and its artifacts. When shipping is authorized, publish that verified draft
|
||||
and confirm its tag and downstream deployment. Do not auto-release every push to `main`, infer
|
||||
version bumps solely from commit prefixes, overwrite published versions, or push release tags
|
||||
before the draft is verified.
|
||||
|
||||
## Running and looking at the app
|
||||
- `cargo run --release -p solvecraft -- --sample --control PORT` (sample design + control channel).
|
||||
Pick a free port. Without a display, run it under `Xvfb :99` with `DISPLAY=:99`.
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
# Releasing SolveCraft
|
||||
|
||||
Releases are deliberate operations that an agent can complete end to end. Development lands
|
||||
on `main`; pushing the selected commit to `release` builds all packages and creates a **draft**
|
||||
GitHub Release. Publishing that verified draft is a separate operation. Pushing to `main` or
|
||||
pushing a tag does not start the release workflow. There is no automatic commit-message-based
|
||||
version bump and no patch release for every merge.
|
||||
|
||||
## Choose and prepare the version
|
||||
|
||||
1. Fetch `origin` and its tags. Inspect the previous published release and the changes since
|
||||
it. Choose one version for the complete batch, based on user-visible behavior:
|
||||
- Patch: compatible fixes, reliability improvements, and packaging corrections.
|
||||
- Minor: new compatible capabilities. While on `0.x`, also use a minor bump for breaking
|
||||
changes and explain the break and any migration in the notes.
|
||||
- Major: incompatible public behavior or file-format/API changes after `1.0`. Moving to
|
||||
`1.0` is an explicit product decision, not something inferred from a commit prefix.
|
||||
- Use `-rc.1`, `-rc.2`, etc. when a release candidate is requested.
|
||||
Documentation-only changes normally wait for the next release. State the reason for the
|
||||
chosen bump in the release notes. Follow any version or release scope specified by the owner.
|
||||
2. Update `[workspace.package] version` in root `Cargo.toml`. Then run
|
||||
`cargo metadata --format-version 1 --no-deps > /dev/null` to refresh workspace package
|
||||
versions in `Cargo.lock`. Inspect the diff and keep unrelated dependency changes out.
|
||||
SolveCraft does not currently implement the siblings' `cargo xtask version set` command.
|
||||
3. Run `cargo xtask ci` and the packaging checks in `.github/workflows/packaging-lint.yml`.
|
||||
Commit the version files through the normal review flow and land them on `main`.
|
||||
Do not use `[skip ci]`. Record the exact commit SHA to release. If it changes, revalidate it.
|
||||
|
||||
## Build the draft
|
||||
|
||||
Use an authenticated maintainer account or GitHub App with the repository's normal release
|
||||
permissions. Follow branch rules; never force-push or bypass required checks.
|
||||
|
||||
```sh
|
||||
git fetch origin --tags
|
||||
git switch release
|
||||
git merge --ff-only origin/release
|
||||
git merge --ff-only origin/main
|
||||
git push origin release
|
||||
```
|
||||
|
||||
For the first release, after this workflow has landed on `main`, create `release` from the
|
||||
validated `origin/main` commit (`git switch -c release origin/main`) and push it. If the branch
|
||||
already exists, use the commands above. Do not create the branch from unrelated work. If a
|
||||
fast-forward fails, inspect and resolve the divergence through the normal review flow.
|
||||
|
||||
Find the `release.yml` Actions run for the recorded SHA and `release` branch, and monitor that
|
||||
specific run. The workflow builds Linux (both architectures, including Flatpak), macOS,
|
||||
Windows, FreeBSD, and web packages. Once every required job succeeds it creates a draft
|
||||
`SolveCraft v<version>` with the artifacts, `SHA256SUMS.txt`, install notes, and generated changes.
|
||||
|
||||
A second push with the same untagged version can rebuild and replace that draft's complete
|
||||
asset set. Existing release notes are preserved on rebuild; refresh their changes section if
|
||||
new commits were added. An existing tag or published release is never overwritten. Use a new
|
||||
version after publication. Do not push the version tag yourself: publishing the draft creates
|
||||
it at the draft's target commit.
|
||||
|
||||
## Verify and publish
|
||||
|
||||
When the task authorizes shipping a release, agents should complete these steps without
|
||||
requiring the owner to click through the GitHub UI. A request only to prepare a release or
|
||||
change release infrastructure does not itself request publication of the application.
|
||||
|
||||
1. Confirm that the workflow run succeeded for the intended SHA and that the matching release
|
||||
is still a draft targeting that SHA. Avoid publishing while another release build is active.
|
||||
2. Download all assets to a fresh directory. Run `sha256sum --check SHA256SUMS.txt` and check
|
||||
that every expected platform/architecture is present. Smoke-test the built CLI's `--version`
|
||||
and an example model on an available platform. Inspect the package-check job output for
|
||||
other platforms; report any platform you could not exercise yourself.
|
||||
3. Review the generated notes. Add a concise description of features, fixes, breaking changes,
|
||||
and known limitations. Keep the installation instructions. Packages are currently unsigned;
|
||||
do not claim otherwise. Resolve failed checks or missing artifacts before publication.
|
||||
4. Publish the verified draft using `gh` or the equivalent GitHub release API. For example:
|
||||
|
||||
```sh
|
||||
# Substitute the version that was actually built and verified.
|
||||
gh release edit v0.2.0 --draft=false --latest
|
||||
# For a release candidate instead:
|
||||
gh release edit v0.2.0-rc.1 --draft=false --prerelease --latest=false
|
||||
```
|
||||
|
||||
5. Confirm that the release is public and its tag resolves to the recorded SHA. Report the
|
||||
release URL, version, checks performed, and any remaining limitations.
|
||||
|
||||
## Dry runs and retries
|
||||
|
||||
`gh workflow run release.yml --ref <branch> -f draft=false` builds packages and uploads the
|
||||
`release-assets` Actions artifact without creating or publishing a release. For an explicit
|
||||
retry that creates/updates a draft, use `--ref release -f draft=true`. Draft creation from
|
||||
another branch is rejected. A rerun must still use an unpublished, untagged version.
|
||||
|
||||
## Flatpak repository coordination
|
||||
|
||||
The Flatpak repository proposed in PR #39 consumes **published** releases, not these drafts.
|
||||
Keep that deployment separate from building the application. A publication made with an
|
||||
Actions `GITHUB_TOKEN` does not trigger downstream `release: published` workflows; in that
|
||||
case explicitly dispatch the Flatpak workflow with the published tag, or use an appropriately
|
||||
authorized maintainer/App token for publication. Do not assume the repository updated: check
|
||||
the deployment run. Coordinate its GitHub Pages output with the existing docs website.
|
||||
|
||||
## Repository setup
|
||||
|
||||
The `release` branch is advanced only for intentional release batches. Restrict its writers to
|
||||
maintainers/release agents through repository rules if required by the owner's policy. The
|
||||
workflow needs `contents: write` only in its draft-creation job. It does not push commits,
|
||||
calculate versions, or require a semantic-release bot to bypass `main` protections.
|
||||
|
||||
This workflow replaces the every-push-to-main semantic-release proposal in PR #38; do not
|
||||
merge that automation alongside this release model without redesigning its triggers.
|
||||
Reference in new issue
Block a user