diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml index 600bf4d3..006b1c3e 100644 --- a/.github/workflows/docker.yaml +++ b/.github/workflows/docker.yaml @@ -46,100 +46,7 @@ jobs: if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' needs: - php_syntax_errors - runs-on: ubuntu-latest - strategy: - matrix: - php-version: - - 8.4 - env: - extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Setup PHP Action - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: ${{ env.extensions }} - coverage: xdebug - tools: pecl, composer - - - name: Install Composer dependencies - uses: ramsey/composer-install@4.0.0 - - - name: Install pnpm - uses: pnpm/action-setup@v6 - - - name: Use Node.js 24 - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - - - name: Install - run: pnpm install --frozen-lockfile - - - name: Compile Front-end - run: pnpm build - - - name: Apply tests ${{ matrix.php-version }} - run: php artisan test - - release_artifact_build: - name: 🏗️ Build / Upload - Release File - if: github.event_name == 'release' - needs: - - tests - runs-on: ubuntu-latest - env: - extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 8.4 - extensions: ${{ env.extensions }} - coverage: none - - - name: Install Composer dependencies - uses: ramsey/composer-install@4.0.0 - with: - composer-options: --no-dev - - - name: Install pnpm - uses: pnpm/action-setup@v6 - - - name: Use Node.js 24 - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: pnpm - - - name: Install - run: pnpm install --frozen-lockfile - - - name: Compile Front-end - run: pnpm build - - - name: Build Dist - run: | - make clean dist - - - name: Upload package - uses: svenstaro/upload-release-action@v2 - with: - repo_token: ${{ github.token }} - file: InvoiceShelf.zip - asset_name: InvoiceShelf.zip - tag: ${{ github.ref }} - overwrite: true - + uses: ./.github/workflows/tests.yaml # Registration lives in its own job, and fetches the published asset rather than # reusing the build job's working directory. That decoupling is deliberate: the @@ -151,14 +58,13 @@ jobs: # injection from the release body. register_release: name: 📡 Register release on the updater - # always() is required because release_artifact_build is skipped on a manual - # dispatch, and a job needing a skipped job is skipped too. + # release.yaml attaches the package before publishing, so by the time this + # runs the asset is already there and no build dependency is needed. That also + # retires the always() dance this condition previously required to survive a + # skipped build job on a manual dispatch. if: >- - always() && - ((github.event_name == 'release' && needs.release_artifact_build.result == 'success') || - (github.event_name == 'workflow_dispatch' && inputs.register_tag != '')) - needs: - - release_artifact_build + github.event_name == 'release' || + (github.event_name == 'workflow_dispatch' && inputs.register_tag != '') runs-on: ubuntu-latest steps: diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..21dbbf6b --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,97 @@ +name: Release + +# Tagging is the trigger. Both spellings are accepted because every tag to date +# is bare (3.0.0-alpha.1, 2.4.2) while this workflow previously listened only for +# "v*" — so it had never once fired, and tagging by the established convention +# produced silence. +on: + push: + tags: + - '[0-9]*' + - 'v[0-9]*' + +permissions: + contents: write + +# Which major owns GitHub's "Latest release" pointer. Same value and same meaning +# as in docker.yaml, which gates the moving :latest image tags on it — bump both +# here and on 3.x when 3.0.0 GA is tagged. +env: + LATEST_MAJOR: "2" + +jobs: + tests: + uses: ./.github/workflows/tests.yaml + + release: + name: Build & Release + needs: + - tests + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: 8.4 + extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip + tools: composer + + - name: Read the release notes from CHANGELOG.md + env: + TAG: ${{ github.ref_name }} + run: | + if ! php .github/scripts/changelog-section.php "$TAG" > /tmp/notes.md; then + echo "::error::No CHANGELOG.md section for $TAG — add one before tagging." + exit 1 + fi + echo "Using the CHANGELOG.md section for $TAG" + + - name: Install pnpm + uses: pnpm/action-setup@v6 + + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + + # `make dist` owns the artifact: it installs composer/pnpm dependencies, + # builds the frontend, assembles the package and generates the manifest. + # This workflow used to hand-copy the same file list a second time, with + # docker.yaml then overwriting the result — two copies of one list, and job + # ordering deciding what users received. + - name: Build the release package + run: make clean dist + + # Created as a draft with the package already attached, then published as a + # separate step. `release: published` therefore fires only once the tests + # have passed and the asset is in place, so downstream registration can + # never race the upload — and a failed run leaves no release at all rather + # than a published one nobody can download. + - name: Create the draft release + uses: softprops/action-gh-release@v3 + with: + files: InvoiceShelf.zip + body_path: /tmp/notes.md + draft: true + prerelease: ${{ contains(github.ref_name, '-') }} + + - name: Publish it + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.ref_name }} + # GitHub's "Latest release" pointer, gated exactly as docker.yaml gates + # its moving image tags — so a 3.0.0 alpha cannot displace 2.4.x as the + # release users are shown first while 2.x is still the stable line. + IS_LATEST: ${{ !contains(github.ref_name, '-') && startsWith(github.ref_name, format('{0}.', env.LATEST_MAJOR)) }} + run: | + if [ "$IS_LATEST" = "true" ]; then + gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false --latest + else + gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false --latest=false + fi + echo "Published $TAG (latest=$IS_LATEST)" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..3c880cef --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,51 @@ +name: Tests + +# Called by docker.yaml and release.yaml rather than written out in both. The +# release artifact's file list already existed in two places on the 3.x branch +# and drift was only a matter of time; the test definition should not repeat it. +on: + workflow_call: + +jobs: + tests: + name: PHP Tests + runs-on: ubuntu-latest + strategy: + matrix: + php-version: + - 8.4 + env: + extensions: bcmath, curl, dom, gd, imagick, json, libxml, mbstring, pcntl, pdo, pdo_mysql, zip + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup PHP Action + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-version }} + extensions: ${{ env.extensions }} + coverage: xdebug + tools: pecl, composer + + - name: Install Composer dependencies + uses: ramsey/composer-install@4.0.0 + + - name: Install pnpm + uses: pnpm/action-setup@v6 + + - name: Use Node.js 24 + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + + - name: Install + run: pnpm install --frozen-lockfile + + - name: Compile Front-end + run: pnpm build + + - name: Apply tests ${{ matrix.php-version }} + run: php artisan test