Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
docs/architecture/boatstack-v2-*.md text eol=lf
docs/architecture/boatstack-v2-*.mmd text eol=lf
docs/architecture/boatstack-v2-*.json text eol=lf
403 changes: 187 additions & 216 deletions .github/tests/test_detached_supervision.py

Large diffs are not rendered by default.

421 changes: 316 additions & 105 deletions .github/tests/test_repository_contract.py

Large diffs are not rendered by default.

33 changes: 19 additions & 14 deletions .github/workflows/auto-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,27 +35,27 @@ jobs:
ref: main
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- uses: actions/setup-go@v7
with:
go-version-file: boatstack/go.mod
cache-dependency-path: boatstack/go.mod
- name: Detect release-bearing change
id: classify
shell: bash
run: |
if [[ ! -f boatstack/release.go ]]; then
echo "The release classifier is not projected yet; no tag will be created."
echo "release_required=false" >> "$GITHUB_OUTPUT"
exit 0
fi
latest_tag="$(git describe --tags --abbrev=0 --match 'v[0-9]*' 2>/dev/null || true)"
if [[ -z "$latest_tag" ]]; then
echo "BLOCKED: automatic patch releases require an existing stable tag." >&2
exit 1
fi
classification="$(cd boatstack && go run ./cmd/boatstack-helper \
release-classify --repo .. --base "$latest_tag" --head HEAD)"
printf '%s\n' "$classification" >> "$GITHUB_OUTPUT"
rewritten="$(git diff --name-only --diff-filter=MD --no-renames "$latest_tag" HEAD -- 'release-notes/*.md')"
if [[ -n "$rewritten" ]]; then
echo "BLOCKED: release notes are append-only:" >&2
printf ' %s\n' "$rewritten" >&2
exit 1
fi
added="$(git diff --name-only --diff-filter=A --no-renames "$latest_tag" HEAD -- 'release-notes/*.md')"
if [[ -n "$added" ]]; then
echo "release_required=true" >> "$GITHUB_OUTPUT"
else
echo "release_required=false" >> "$GITHUB_OUTPUT"
fi
echo "latest_tag=$latest_tag" >> "$GITHUB_OUTPUT"
- name: Create next verified patch tag
if: steps.classify.outputs.release_required == 'true'
Expand All @@ -64,8 +64,13 @@ jobs:
LATEST_TAG: ${{ steps.classify.outputs.latest_tag }}
shell: bash
run: |
next_tag="$(cd boatstack && go run ./cmd/boatstack-helper \
next-patch --version "$LATEST_TAG")"
version="${LATEST_TAG#v}"
IFS=. read -r major minor patch <<< "$version"
if [[ ! "$major" =~ ^[0-9]+$ || ! "$minor" =~ ^[0-9]+$ || ! "$patch" =~ ^[0-9]+$ ]]; then
echo "BLOCKED: latest tag is not a stable semantic version: $LATEST_TAG" >&2
exit 1
fi
next_tag="v${major}.${minor}.$((patch + 1))"
if git rev-parse --verify --quiet "refs/tags/$next_tag"; then
echo "BLOCKED: tag already exists: $next_tag" >&2
exit 1
Expand Down
57 changes: 47 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ on:
push:
tags: ["v*"]
workflow_dispatch:
inputs:
prerelease_tag:
description: "New prerelease tag to publish from the selected branch (for example v2.0.0-rc.1)"
required: true
type: string

permissions:
contents: write
Expand Down Expand Up @@ -47,7 +52,7 @@ jobs:
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: "0"
ASSET: ${{ matrix.asset }}
VERSION: ${{ github.ref_name }}
VERSION: ${{ inputs.prerelease_tag || github.ref_name }}
run: |
source_commit="$(git rev-parse HEAD)"
mkdir -p dist
Expand All @@ -63,7 +68,7 @@ jobs:
path: dist/${{ matrix.asset }}*

release:
if: startsWith(github.ref, 'refs/tags/')
if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch'
needs: build
runs-on: ubuntu-latest
steps:
Expand All @@ -77,26 +82,49 @@ jobs:
- name: Publish release assets
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.prerelease_tag || github.ref_name }}
RELEASE_SOURCE: ${{ github.sha }}
MANUAL_PRERELEASE: ${{ github.event_name == 'workflow_dispatch' }}
shell: bash
run: |
previous_tag="$(git describe --tags --abbrev=0 "${GITHUB_REF_NAME}^" 2>/dev/null || true)"
if [[ "$MANUAL_PRERELEASE" == true ]]; then
[[ "$GITHUB_REF" == refs/heads/* ]] || {
echo "BLOCKED: manual prereleases must be dispatched from a branch" >&2
exit 2
}
[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$ ]] || {
echo "BLOCKED: manual release tags must use vMAJOR.MINOR.PATCH-rc.NUMBER" >&2
exit 2
}
if git ls-remote --exit-code --tags origin "refs/tags/$RELEASE_TAG" >/dev/null 2>&1; then
echo "BLOCKED: release tag $RELEASE_TAG already exists" >&2
exit 2
fi
remote_source="$(git ls-remote origin "refs/heads/$GITHUB_REF_NAME" | awk 'NR == 1 {print $1}')"
[[ -n "$remote_source" && "$remote_source" == "$RELEASE_SOURCE" ]] || {
echo "BLOCKED: selected branch no longer resolves to exact source $RELEASE_SOURCE" >&2
exit 2
}
fi

previous_tag="$(git describe --tags --abbrev=0 "${RELEASE_SOURCE}^" 2>/dev/null || true)"
if [[ -n "$previous_tag" ]]; then
rewritten_notes=()
while IFS= read -r note; do
rewritten_notes+=("$note")
done < <(git diff --name-only --diff-filter=MD --no-renames \
"$previous_tag" "$GITHUB_REF_NAME" -- 'release-notes/*.md')
"$previous_tag" "$RELEASE_SOURCE" -- 'release-notes/*.md')
release_notes=()
while IFS= read -r note; do
release_notes+=("$note")
done < <(git diff --name-only --diff-filter=A --no-renames \
"$previous_tag" "$GITHUB_REF_NAME" -- 'release-notes/*.md' | LC_ALL=C sort)
"$previous_tag" "$RELEASE_SOURCE" -- 'release-notes/*.md' | LC_ALL=C sort)
else
rewritten_notes=()
release_notes=()
while IFS= read -r note; do
release_notes+=("$note")
done < <(git ls-tree -r --name-only "$GITHUB_REF_NAME" -- \
done < <(git ls-tree -r --name-only "$RELEASE_SOURCE" -- \
'release-notes/*.md' | LC_ALL=C sort)
fi
if (( ${#rewritten_notes[@]} > 0 )); then
Expand All @@ -117,7 +145,16 @@ jobs:
echo
done
} > "$release_body"
gh release create "${GITHUB_REF_NAME}" dist/* \
--repo "$GITHUB_REPOSITORY" \
--notes-file "$release_body" \
--verify-tag
if [[ "$MANUAL_PRERELEASE" == true ]]; then
gh release create "$RELEASE_TAG" dist/* \
--repo "$GITHUB_REPOSITORY" \
--target "$RELEASE_SOURCE" \
--title "$RELEASE_TAG" \
--notes-file "$release_body" \
--prerelease
else
gh release create "$RELEASE_TAG" dist/* \
--repo "$GITHUB_REPOSITORY" \
--notes-file "$release_body" \
--verify-tag
fi
Loading
Loading