Merge pull request #55 from bherbruck/ci/agent-managed-releases
Use agent-managed draft releases from the release branch
This commit is contained in:
6 files changed
+178
-26
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`.
|
||||
|
||||
@@ -179,6 +179,8 @@ fn deepest(a: &solvecraft_geom::Mesh, b: &solvecraft_geom::Mesh, tol: f64) -> f6
|
||||
|
||||
/// Ray parity along a skew direction (no axis-aligned edge hits).
|
||||
fn inside(p: Vec3, tris: &[[Vec3; 3]]) -> bool {
|
||||
// Fixed ray coordinates, not an approximation used to compute Euler's constant.
|
||||
#[allow(clippy::approx_constant)]
|
||||
let dir = Vec3::new(0.577_215_664_9, 0.327_118_441_9, 0.751_891_438_7);
|
||||
let mut n = 0;
|
||||
for [a, b, c] in tris {
|
||||
|
||||
@@ -2765,10 +2765,8 @@ pub fn for_feature(app: &SolveApp, id: u64, marker: Option<usize>) -> Option<Dia
|
||||
if let Some(inp) = d.inputs.first_mut() {
|
||||
inp.items = items.into_iter().map(|index| Sel::Profile { sketch: *sketch, index }).collect();
|
||||
}
|
||||
for (k, v) in [("targets", (!targets.is_empty()).then(|| json!(targets)))] {
|
||||
if let Some(v) = v {
|
||||
d.extra.insert(k.into(), v);
|
||||
}
|
||||
if !targets.is_empty() {
|
||||
d.extra.insert("targets".into(), json!(targets));
|
||||
}
|
||||
d
|
||||
}
|
||||
|
||||
@@ -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