diff --git a/.github/workflows/coolify-next-build.yml b/.github/workflows/coolify-next-build.yml new file mode 100644 index 0000000000..9985edb411 --- /dev/null +++ b/.github/workflows/coolify-next-build.yml @@ -0,0 +1,152 @@ +name: Build Coolify Next + +on: + push: + branches: [next] + paths-ignore: + - .github/workflows/coolify-helper.yml + - .github/workflows/coolify-helper-next.yml + - .github/workflows/coolify-realtime.yml + - .github/workflows/coolify-realtime-next.yml + - .github/workflows/pr-quality.yaml + - docker/coolify-helper/Dockerfile + - docker/coolify-realtime/Dockerfile + - docker/testing-host/Dockerfile + - templates/** + - CHANGELOG.md + +permissions: + contents: read + packages: write + +concurrency: + group: coolify-next-build + cancel-in-progress: false + +env: + GITHUB_REGISTRY: ghcr.io + DOCKER_REGISTRY: docker.io + IMAGE_NAME: coollabsio/coolify + +jobs: + prepare: + runs-on: ubuntu-24.04 + outputs: + rc_version: ${{ steps.version.outputs.rc_version }} + short_sha: ${{ steps.version.outputs.short_sha }} + version: ${{ steps.version.outputs.version }} + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - name: Resolve next version + id: version + run: | + RC_VERSION=$(jq -r '.coolify.nightly.version' versions.json) + if [[ ! "${RC_VERSION}" =~ ^[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then + echo "Invalid next RC version: ${RC_VERSION}" + exit 1 + fi + + SHORT_SHA="${GITHUB_SHA::7}" + VERSION="${RC_VERSION}.${SHORT_SHA}" + echo "rc_version=${RC_VERSION}" >> "$GITHUB_OUTPUT" + echo "short_sha=${SHORT_SHA}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + build: + needs: prepare + strategy: + matrix: + include: + - arch: amd64 + platform: linux/amd64 + runner: ubuntu-24.04 + - arch: aarch64 + platform: linux/aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - uses: docker/setup-buildx-action@v3 + + - name: Login to ${{ env.GITHUB_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.GITHUB_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to ${{ env.DOCKER_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push next image (${{ matrix.arch }}) + uses: docker/build-push-action@v6 + with: + context: . + file: docker/production/Dockerfile + platforms: ${{ matrix.platform }} + push: true + build-args: | + COOLIFY_VERSION=${{ needs.prepare.outputs.version }} + tags: | + ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:next-build-${{ needs.prepare.outputs.short_sha }}-${{ matrix.arch }} + ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:next-build-${{ needs.prepare.outputs.short_sha }}-${{ matrix.arch }} + + publish: + needs: [prepare, build] + runs-on: ubuntu-24.04 + steps: + - uses: docker/setup-buildx-action@v3 + + - name: Login to ${{ env.GITHUB_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.GITHUB_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to ${{ env.DOCKER_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Publish next manifest on ${{ env.GITHUB_REGISTRY }} + env: + REGISTRY: ${{ env.GITHUB_REGISTRY }} + SHA: ${{ needs.prepare.outputs.short_sha }} + VERSION: ${{ needs.prepare.outputs.version }} + run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + SOURCE="next-build-${SHA}" + docker buildx imagetools create \ + "${IMAGE}:${SOURCE}-amd64" \ + "${IMAGE}:${SOURCE}-aarch64" \ + --tag "${IMAGE}:sha-${SHA}" \ + --tag "${IMAGE}:${VERSION}" \ + --tag "${IMAGE}:next" + + - name: Publish next manifest on ${{ env.DOCKER_REGISTRY }} + env: + REGISTRY: ${{ env.DOCKER_REGISTRY }} + SHA: ${{ needs.prepare.outputs.short_sha }} + VERSION: ${{ needs.prepare.outputs.version }} + run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + SOURCE="next-build-${SHA}" + docker buildx imagetools create \ + "${IMAGE}:${SOURCE}-amd64" \ + "${IMAGE}:${SOURCE}-aarch64" \ + --tag "${IMAGE}:sha-${SHA}" \ + --tag "${IMAGE}:${VERSION}" \ + --tag "${IMAGE}:next" diff --git a/.github/workflows/coolify-rc-release.yml b/.github/workflows/coolify-rc-release.yml new file mode 100644 index 0000000000..90005a97d2 --- /dev/null +++ b/.github/workflows/coolify-rc-release.yml @@ -0,0 +1,304 @@ +name: Release Coolify RC +run-name: ${{ inputs.tag }} + +on: + workflow_dispatch: + inputs: + tag: + description: Existing draft prerelease tag (for example, v4.4-rc.1) + required: true + type: string + +permissions: {} + +concurrency: + group: coolify-rc-release + cancel-in-progress: false + +env: + GITHUB_REGISTRY: ghcr.io + DOCKER_REGISTRY: docker.io + IMAGE_NAME: coollabsio/coolify + +jobs: + validate: + runs-on: ubuntu-24.04 + permissions: + contents: write + outputs: + release_id: ${{ steps.draft.outputs.release_id }} + version: ${{ steps.version.outputs.version }} + steps: + - name: Reject releases outside next + if: ${{ github.ref != 'refs/heads/next' }} + run: | + echo "RC releases must run from the next branch, not ${{ github.ref }}." + exit 1 + + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + persist-credentials: false + + - name: Validate version + id: version + env: + TAG_NAME: ${{ inputs.tag }} + run: | + if [[ ! "${TAG_NAME}" =~ ^v[0-9]+\.[0-9]+-rc\.[0-9]+$ ]]; then + echo "Unsupported RC tag: ${TAG_NAME}" + exit 1 + fi + + VERSION="${TAG_NAME#v}" + CONFIG_VERSION=$(jq -r '.coolify.nightly.version' versions.json) + if [[ "${CONFIG_VERSION}" != "${VERSION}" ]]; then + echo "RC tag ${VERSION} does not match nightly version ${CONFIG_VERSION}." + exit 1 + fi + + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Validate and pin draft prerelease + id: draft + uses: actions/github-script@v8 + env: + TAG_NAME: ${{ inputs.tag }} + with: + script: | + const releases = await github.paginate(github.rest.repos.listReleases, { + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 100, + }); + const release = releases.find((candidate) => candidate.tag_name === process.env.TAG_NAME); + + if (!release) { + core.setFailed(`Create a draft prerelease for ${process.env.TAG_NAME} before running this workflow.`); + return; + } + if (!release.draft) { + core.setFailed(`Release ${process.env.TAG_NAME} must still be a draft.`); + return; + } + if (!release.prerelease) { + core.setFailed(`RC release ${process.env.TAG_NAME} must be marked as a prerelease.`); + return; + } + if (!release.body?.trim()) { + core.setFailed(`Draft prerelease ${process.env.TAG_NAME} must contain reviewed release notes.`); + return; + } + + try { + await github.rest.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${process.env.TAG_NAME}`, + }); + core.setFailed(`Git tag ${process.env.TAG_NAME} already exists.`); + return; + } catch (error) { + if (error.status !== 404) throw error; + } + + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: release.id, + tag_name: process.env.TAG_NAME, + target_commitish: context.sha, + prerelease: true, + }); + core.setOutput('release_id', release.id); + + build: + needs: validate + permissions: + contents: read + packages: write + strategy: + matrix: + include: + - arch: amd64 + platform: linux/amd64 + runner: ubuntu-24.04 + - arch: aarch64 + platform: linux/aarch64 + runner: ubuntu-24.04-arm + runs-on: ${{ matrix.runner }} + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + + - uses: docker/setup-buildx-action@v3 + + - name: Login to ${{ env.GITHUB_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.GITHUB_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to ${{ env.DOCKER_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push RC image (${{ matrix.arch }}) + uses: docker/build-push-action@v6 + with: + context: . + file: docker/production/Dockerfile + platforms: ${{ matrix.platform }} + push: true + build-args: | + COOLIFY_VERSION=${{ needs.validate.outputs.version }} + tags: | + ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:rc-release-${{ needs.validate.outputs.version }}-${{ github.sha }}-${{ matrix.arch }} + ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:rc-release-${{ needs.validate.outputs.version }}-${{ github.sha }}-${{ matrix.arch }} + + revalidate: + needs: [validate, build] + runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: Revalidate draft prerelease + uses: actions/github-script@v8 + env: + RELEASE_ID: ${{ needs.validate.outputs.release_id }} + TAG_NAME: ${{ inputs.tag }} + with: + script: | + const releaseId = Number(process.env.RELEASE_ID); + const { data: release } = await github.rest.repos.getRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: releaseId, + }); + + if (release.tag_name !== process.env.TAG_NAME || !release.draft || !release.prerelease) { + core.setFailed(`Draft prerelease ${process.env.TAG_NAME} changed while the images were building.`); + return; + } + if (!release.body?.trim()) { + core.setFailed(`Draft prerelease ${process.env.TAG_NAME} no longer contains release notes.`); + return; + } + if (release.target_commitish !== context.sha) { + core.setFailed(`Draft prerelease ${process.env.TAG_NAME} no longer targets ${context.sha}.`); + return; + } + + try { + await github.rest.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${process.env.TAG_NAME}`, + }); + core.setFailed(`Git tag ${process.env.TAG_NAME} was created while the images were building.`); + } catch (error) { + if (error.status !== 404) throw error; + } + + publish: + needs: [validate, build, revalidate] + runs-on: ubuntu-24.04 + permissions: + contents: write + packages: write + steps: + - uses: docker/setup-buildx-action@v3 + + - name: Login to ${{ env.GITHUB_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.GITHUB_REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Login to ${{ env.DOCKER_REGISTRY }} + uses: docker/login-action@v3 + with: + registry: ${{ env.DOCKER_REGISTRY }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Publish RC and next on ${{ env.GITHUB_REGISTRY }} + env: + REGISTRY: ${{ env.GITHUB_REGISTRY }} + VERSION: ${{ needs.validate.outputs.version }} + run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + SOURCE="rc-release-${VERSION}-${GITHUB_SHA}" + docker buildx imagetools create \ + "${IMAGE}:${SOURCE}-amd64" \ + "${IMAGE}:${SOURCE}-aarch64" \ + --tag "${IMAGE}:${VERSION}" \ + --tag "${IMAGE}:next" + + - name: Publish RC and next on ${{ env.DOCKER_REGISTRY }} + env: + REGISTRY: ${{ env.DOCKER_REGISTRY }} + VERSION: ${{ needs.validate.outputs.version }} + run: | + IMAGE="${REGISTRY}/${IMAGE_NAME}" + SOURCE="rc-release-${VERSION}-${GITHUB_SHA}" + docker buildx imagetools create \ + "${IMAGE}:${SOURCE}-amd64" \ + "${IMAGE}:${SOURCE}-aarch64" \ + --tag "${IMAGE}:${VERSION}" \ + --tag "${IMAGE}:next" + + - name: Publish reviewed draft prerelease + uses: actions/github-script@v8 + env: + RELEASE_ID: ${{ needs.validate.outputs.release_id }} + TAG_NAME: ${{ inputs.tag }} + with: + script: | + const releaseId = Number(process.env.RELEASE_ID); + const { data: release } = await github.rest.repos.getRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: releaseId, + }); + + if (release.tag_name !== process.env.TAG_NAME || !release.draft || !release.prerelease) { + core.setFailed(`Draft prerelease ${process.env.TAG_NAME} changed while the images were building.`); + return; + } + if (!release.body?.trim()) { + core.setFailed(`Draft prerelease ${process.env.TAG_NAME} no longer contains release notes.`); + return; + } + if (release.target_commitish !== context.sha) { + core.setFailed(`Draft prerelease ${process.env.TAG_NAME} no longer targets ${context.sha}.`); + return; + } + + try { + await github.rest.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `tags/${process.env.TAG_NAME}`, + }); + core.setFailed(`Git tag ${process.env.TAG_NAME} was created while the images were building.`); + return; + } catch (error) { + if (error.status !== 404) throw error; + } + + await github.rest.repos.updateRelease({ + owner: context.repo.owner, + repo: context.repo.repo, + release_id: Number(process.env.RELEASE_ID), + tag_name: process.env.TAG_NAME, + target_commitish: context.sha, + prerelease: true, + draft: false, + }); diff --git a/.github/workflows/coolify-staging-build.yml b/.github/workflows/coolify-staging-build.yml deleted file mode 100644 index ccbd141295..0000000000 --- a/.github/workflows/coolify-staging-build.yml +++ /dev/null @@ -1,134 +0,0 @@ -name: Staging Build - -on: - push: - branches-ignore: - - main - - v3.x - - '**v5.x**' - paths-ignore: - - .github/workflows/coolify-helper.yml - - .github/workflows/coolify-helper-next.yml - - .github/workflows/coolify-realtime.yml - - .github/workflows/coolify-realtime-next.yml - - .github/workflows/pr-quality.yaml - - docker/coolify-helper/Dockerfile - - docker/coolify-realtime/Dockerfile - - docker/testing-host/Dockerfile - - templates/** - - CHANGELOG.md - -permissions: - contents: read - packages: write - -env: - GITHUB_REGISTRY: ghcr.io - DOCKER_REGISTRY: docker.io - IMAGE_NAME: "coollabsio/coolify" - -jobs: - build-push: - strategy: - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-24.04 - - arch: aarch64 - platform: linux/aarch64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Sanitize branch name for Docker tag - id: sanitize - run: | - # Replace slashes and other invalid characters with dashes - SANITIZED_NAME=$(echo "${{ github.ref_name }}" | sed 's/[\/]/-/g') - echo "tag=${SANITIZED_NAME}" >> $GITHUB_OUTPUT - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and Push Image (${{ matrix.arch }}) - uses: docker/build-push-action@v6 - with: - context: . - file: docker/production/Dockerfile - platforms: ${{ matrix.platform }} - push: true - tags: | - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-${{ matrix.arch }} - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-${{ matrix.arch }} - cache-from: | - type=gha,scope=build-${{ matrix.arch }} - type=registry,ref=${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache-${{ matrix.arch }} - cache-to: type=gha,mode=max,scope=build-${{ matrix.arch }} - - merge-manifest: - runs-on: ubuntu-24.04 - needs: build-push - steps: - - uses: actions/checkout@v5 - with: - persist-credentials: false - - - name: Sanitize branch name for Docker tag - id: sanitize - run: | - # Replace slashes and other invalid characters with dashes - SANITIZED_NAME=$(echo "${{ github.ref_name }}" | sed 's/[\/]/-/g') - echo "tag=${SANITIZED_NAME}" >> $GITHUB_OUTPUT - - - uses: docker/setup-buildx-action@v3 - - - name: Login to ${{ env.GITHUB_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.GITHUB_REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Create & publish manifest on ${{ env.GITHUB_REGISTRY }} - run: | - docker buildx imagetools create \ - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-amd64 \ - ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-aarch64 \ - --tag ${{ env.GITHUB_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }} - - - name: Create & publish manifest on ${{ env.DOCKER_REGISTRY }} - run: | - docker buildx imagetools create \ - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-amd64 \ - ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }}-aarch64 \ - --tag ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sanitize.outputs.tag }} - - - uses: sarisia/actions-status-discord@v1 - if: always() - with: - webhook: ${{ secrets.DISCORD_WEBHOOK_DEV_RELEASE_CHANNEL }} diff --git a/RELEASE.md b/RELEASE.md index d278d76902..3733392a48 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -9,7 +9,7 @@ | `feature/*` | New features based on and merged into `next` | | `hotfix/X.Y.Z` | Production fixes based on `main` | -Release workflows never edit or commit versions. Set the intended version in `config/constants.php` before running a release workflow. +Release workflows never edit or commit versions. Stable versions come from `config/constants.php`; RC versions come from `coolify.nightly.version` in `versions.json` and `other/nightly/versions.json`. ## Where changes go @@ -25,11 +25,12 @@ feature/* → next → RC ``` 1. Merge feature branches into `next`. -2. Set the intended RC version on `next`, such as `4.4-rc.1`. -3. Regular builds publish `sha-`, `4.4-rc.1.`, and the moving `next` tag. +2. Set `coolify.nightly.version` in both version files to the intended RC, such as `4.4-rc.1`. +3. Regular `next` builds publish `sha-`, `4.4-rc.1.`, and the moving `next` tag. They never publish the exact `4.4-rc.1` tag. 4. Create a reviewed draft GitHub Release named `v4.4-rc.1` and mark it as a prerelease. -5. Run the RC workflow from `next`. It publishes `4.4-rc.1`, updates `next`, and publishes the draft. -6. Advance `next` to the next intended RC version. +5. Run **Release Coolify RC** manually from `next` and enter `v4.4-rc.1`. +6. The workflow validates the draft and configured nightly version, builds the exact RC, publishes `4.4-rc.1`, updates `next`, and publishes the draft prerelease. +7. Advance `coolify.nightly.version` to the next intended RC version. ## Stable release flow diff --git a/other/nightly/versions.json b/other/nightly/versions.json index ae1ecfae54..440ad36160 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -4,7 +4,7 @@ "version": "4.3.10" }, "nightly": { - "version": "4.3.11" + "version": "4.4-rc.1" }, "helper": { "version": "1.0.15" diff --git a/tests/Unit/ProductionImageWorkflowTest.php b/tests/Unit/ProductionImageWorkflowTest.php index 738b501d57..5fcada3091 100644 --- a/tests/Unit/ProductionImageWorkflowTest.php +++ b/tests/Unit/ProductionImageWorkflowTest.php @@ -25,7 +25,7 @@ it('publishes v4 branch builds under the commit sha with a traceable internal ve ->and($constants) ->toContain("'version' => env('COOLIFY_VERSION') ?: '4.3.10'") ->and($versions['coolify']['v4']['version'])->toBe('4.3.10') - ->and($versions['coolify']['nightly']['version'])->toBe('4.3.11') + ->and($versions['coolify']['nightly']['version'])->toBe('4.4-rc.1') ->and($nightlyVersions)->toBe($versions); }); @@ -34,6 +34,12 @@ it('orders a maintenance development build before its stable release', function ->and(version_compare('4.3.2', '4.3.2-dev.d64cbda3e', '>'))->toBeTrue(); }); +it('orders rolling and exact release candidates before the stable release', function () { + expect(version_compare('4.4-rc.1.d64cbda', '4.4-rc.1', '<'))->toBeTrue() + ->and(version_compare('4.4-rc.1', '4.4-rc.2', '<'))->toBeTrue() + ->and(version_compare('4.4-rc.2', '4.4.0', '<'))->toBeTrue(); +}); + it('requires a reviewed draft release before building a stable version', function () { $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-release.yml'); @@ -104,12 +110,45 @@ it('generates the production changelog from main', function () { ->not->toContain('v4.x'); }); -it('excludes main from staging builds', function () { - $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-staging-build.yml'); +it('publishes traceable rolling builds from next without creating an exact rc tag', function () { + $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-next-build.yml'); expect($workflow) - ->toContain(' - main') - ->not->toContain(' - v4.x'); + ->toContain('name: Build Coolify Next') + ->toContain('branches: [next]') + ->toContain('group: coolify-next-build') + ->toContain("jq -r '.coolify.nightly.version' versions.json") + ->toContain('VERSION="${RC_VERSION}.${SHORT_SHA}"') + ->toContain('COOLIFY_VERSION=${{ needs.prepare.outputs.version }}') + ->toContain('--tag "${IMAGE}:sha-${SHA}"') + ->toContain('--tag "${IMAGE}:${VERSION}"') + ->toContain('--tag "${IMAGE}:next"') + ->not->toContain('--tag "${IMAGE}:${RC_VERSION}"') + ->not->toContain('--tag "${IMAGE}:latest"'); +}); + +it('requires a reviewed draft prerelease before publishing an exact rc', function () { + $workflow = file_get_contents(dirname(__DIR__, 2).'/.github/workflows/coolify-rc-release.yml'); + + expect($workflow) + ->toContain('name: Release Coolify RC') + ->toContain('run-name: ${{ inputs.tag }}') + ->toContain("github.ref != 'refs/heads/next'") + ->toContain('group: coolify-rc-release') + ->toContain('^v[0-9]+\\.[0-9]+-rc\\.[0-9]+$') + ->toContain("jq -r '.coolify.nightly.version' versions.json") + ->toContain('release.draft') + ->toContain('!release.prerelease') + ->toContain('release.body?.trim()') + ->toContain('revalidate:') + ->toContain('needs: [validate, build, revalidate]') + ->toContain('COOLIFY_VERSION=${{ needs.validate.outputs.version }}') + ->toContain('--tag "${IMAGE}:${VERSION}"') + ->toContain('--tag "${IMAGE}:next"') + ->toContain('prerelease: true') + ->toContain('actions/github-script@v8') + ->not->toContain('--tag "${IMAGE}:latest"') + ->not->toContain('environment:'); }); it('rebuilds stable images and publishes the reviewed draft after both architectures succeed', function () { diff --git a/versions.json b/versions.json index ae1ecfae54..440ad36160 100644 --- a/versions.json +++ b/versions.json @@ -4,7 +4,7 @@ "version": "4.3.10" }, "nightly": { - "version": "4.3.11" + "version": "4.4-rc.1" }, "helper": { "version": "1.0.15"