diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b5916c9..046be62 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -258,4 +258,8 @@ Releases are cut from GitHub Actions, never from a local machine. Stable versions must match `X.Y.Z` and can only be cut from `main`. -The workflow pushes with a token minted from the Portabase GitHub App (`APP_ID` repository variable, `APP_PRIVATE_KEY` secret), scoped to *Contents: write*. The app must be installed on this repository and allowed to push to `main`. A tag pushed with the default `GITHUB_TOKEN` would not trigger the release workflows. +### Beta releases + +Every push to `dev` publishes a beta automatically (**Beta release** workflow): no version commit, no Discord notification. The version is the next patch of `pyproject.toml` suffixed with the run number (`26.09.2` → `26.09.3b57`), baked into the binaries at build time and published as a GitHub prerelease. Install it with `portabase config channel beta`; stable users never receive it. + +The Bump version workflow pushes with a token minted from the Portabase GitHub App (`APP_ID` repository variable, `APP_PRIVATE_KEY` secret), scoped to *Contents: write*. The app must be installed on this repository and allowed to push to `main`. A tag pushed with the default `GITHUB_TOKEN` would not trigger the release workflows. diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml new file mode 100644 index 0000000..d1eb694 --- /dev/null +++ b/.github/workflows/beta.yml @@ -0,0 +1,58 @@ +name: Beta release + +on: + push: + branches: [dev] + +permissions: {} + +concurrency: + group: beta-${{ github.ref }} + cancel-in-progress: false + +jobs: + version: + name: version + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: read + outputs: + version: ${{ steps.compute.outputs.version }} + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + + - id: compute + env: + RUN_NUMBER: ${{ github.run_number }} + run: | + set -euo pipefail + CURRENT=$(sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml) + if [[ ! "$CURRENT" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(.*)$ ]]; then + echo "::error::cannot parse version '$CURRENT' from pyproject.toml" + exit 1 + fi + MAJOR=${BASH_REMATCH[1]} MINOR=${BASH_REMATCH[2]} PATCH=${BASH_REMATCH[3]} + PATCH=$((10#$PATCH + 1)) + VERSION="$MAJOR.$MINOR.${PATCH}b$RUN_NUMBER" + echo "Beta version: $VERSION" + echo "version=$VERSION" >> "$GITHUB_OUTPUT" + + build: + needs: version + permissions: + contents: read + id-token: write + attestations: write + uses: ./.github/workflows/build.yml + with: + version: ${{ needs.version.outputs.version }} + + publish: + needs: [version, build] + permissions: + contents: write + uses: ./.github/workflows/publish.yml + with: + prerelease: true + tag: ${{ needs.version.outputs.version }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7942dbf..3ddbf7c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,12 @@ name: Build binaries on: workflow_call: + inputs: + version: + description: "Version baked into the binary. Default: the one in pyproject.toml." + required: false + type: string + default: "" permissions: {} @@ -44,6 +50,13 @@ jobs: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - uses: ./.github/actions/setup + # perl rather than sed -i, which differs between GNU and BSD (macOS). + - name: Set version + if: inputs.version != '' + env: + VERSION: ${{ inputs.version }} + run: perl -pi -e 's/^version = ".*"/version = "$ENV{VERSION}"/' pyproject.toml + - name: Build id: build uses: ./.github/actions/build diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 07edf57..10c9e05 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -6,6 +6,11 @@ on: prerelease: required: true type: boolean + tag: + description: "Tag to create on the current commit. Default: the pushed tag." + required: false + type: string + default: "" permissions: {} @@ -21,6 +26,15 @@ jobs: with: fetch-depth: 0 + - name: Create tag + if: inputs.tag != '' + env: + TAG: ${{ inputs.tag }} + run: | + set -euo pipefail + git tag "$TAG" "$GITHUB_SHA" + git push origin "refs/tags/$TAG" + - name: Download binaries uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -37,6 +51,7 @@ jobs: uses: mikepenz/release-changelog-builder-action@c9dc8369bccbc41e0ac887f8fd674f5925d315f7 # v5 with: mode: COMMIT + toTag: ${{ inputs.tag }} configurationJson: | { "template": "#{{CHANGELOG}}", @@ -59,6 +74,7 @@ jobs: - name: Create GitHub release uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: + tag_name: ${{ inputs.tag }} files: dist/* generate_release_notes: false body: ${{ steps.changelog.outputs.changelog }} diff --git a/.plumber.yaml b/.plumber.yaml new file mode 100644 index 0000000..59d1534 --- /dev/null +++ b/.plumber.yaml @@ -0,0 +1,8 @@ +extends: plumber:default +version: "2.0" + +github: + controls: + workflowMustIncludeRequiredActions: + enabled: true + required: gitleaks/gitleaks-action AND aquasecurity/trivy-action AND getplumber/plumber diff --git a/uv.lock b/uv.lock index 357a540..de95f97 100644 --- a/uv.lock +++ b/uv.lock @@ -521,7 +521,7 @@ wheels = [ [[package]] name = "portabase-cli" -version = "26.8.12" +version = "26.9.2" source = { virtual = "." } dependencies = [ { name = "jinja2" },