From 7dcbfb97ba5d20b3ac8f7dc1cbd60f8c35b794ed Mon Sep 17 00:00:00 2001 From: Aaron LaBeau <80424345+biozal@users.noreply.github.com> Date: Fri, 6 Nov 2026 09:42:18 -0600 Subject: [PATCH 1/2] Build the reviewed v0.1.0 tag for a GitHub-only Homebrew test Remove the global update-check override that breaks unit tests. Add an explicit bootstrap workflow that tests the unchanged release commit and publishes GitHub assets using GITHUB_TOKEN, without npm or cross-repository credentials. Co-Authored-By: Codex --- .github/workflows/homebrew-test-release.yml | 149 ++++++++++++++++++++ .github/workflows/release.yml | 14 +- 2 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/homebrew-test-release.yml diff --git a/.github/workflows/homebrew-test-release.yml b/.github/workflows/homebrew-test-release.yml new file mode 100644 index 0000000..c6cd928 --- /dev/null +++ b/.github/workflows/homebrew-test-release.yml @@ -0,0 +1,149 @@ +# One-time Homebrew bootstrap, pinned to the reviewed v0.1.0 source commit. +# Invoke through Release's homebrew-test input. No npm or cross-repository writes. +name: Homebrew v0.1.0 test release + +on: + workflow_call: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ac9510e6ebc1118341a63cf8b8b398d41b88bb91 + fetch-depth: 0 + persist-credentials: false + - name: Verify the reviewed release tag + run: test "$(git rev-parse 'refs/tags/v0.1.0^{commit}')" = "$(git rev-parse HEAD)" + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: "24" + - run: npm ci + - run: npm run lint + - run: npm run typecheck + - run: npm run test:unit + - name: Build the stamped release archive + env: + OFFLINE_TOKEN: ${{ secrets.DQL_OFFLINE_LICENSE }} + DATABASE_ID: ${{ secrets.DITTO_DATABASE_ID }} + run: node --import tsx scripts/release.ts build v0.1.0 + - name: Point the formula at the GitHub release archive + run: | + node --import tsx --input-type=module <<'JS' + import fs from 'node:fs'; + import { digest, formula } from './scripts/release.ts'; + const archive = 'build/release/dittolive-cli-0.1.0.tgz'; + const url = 'https://github.com/getditto/ditto-cli/releases/download/v0.1.0/dittolive-cli-0.1.0.tgz'; + fs.writeFileSync('build/release/dittosh.rb', formula('0.1.0', digest(fs.readFileSync(archive)), url)); + JS + - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: homebrew-v0.1.0 + path: | + build/release/*.tgz + build/release/SHA256SUMS + build/release/dittosh.rb + if-no-files-found: error + + quality: + needs: build + runs-on: macos-15 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ac9510e6ebc1118341a63cf8b8b398d41b88bb91 + persist-credentials: false + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: "24" + - run: npm ci + - name: Integration coverage and end-to-end tests + env: + OFFLINE_TOKEN: ${{ secrets.DQL_OFFLINE_LICENSE }} + DATABASE_ID: ${{ secrets.DITTO_DATABASE_ID }} + run: | + test -n "$OFFLINE_TOKEN" && test -n "$DATABASE_ID" + npm run coverage + npm run test:e2e + + install: + needs: build + strategy: + fail-fast: false + matrix: + os: [macos-15, ubuntu-24.04, ubuntu-24.04-arm] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ac9510e6ebc1118341a63cf8b8b398d41b88bb91 + persist-credentials: false + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: "24" + - run: npm ci + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 + with: + name: homebrew-v0.1.0 + path: build/release + - uses: Homebrew/actions/setup-homebrew@c262868c096d909fc278daa87ed9772967331ea3 + - name: Install and test the exact release archive with Homebrew + env: + HOMEBREW_NO_AUTO_UPDATE: "1" + HOMEBREW_NO_INSTALL_CLEANUP: "1" + run: node --import tsx scripts/release-smoke.ts homebrew v0.1.0 + + publish: + needs: [build, quality, install] + runs-on: ubuntu-24.04 + permissions: + contents: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ac9510e6ebc1118341a63cf8b8b398d41b88bb91 + fetch-depth: 0 + persist-credentials: false + - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 + with: + name: homebrew-v0.1.0 + path: build/release + - name: Verify archive checksum and unchanged tag + run: | + test "$(git rev-parse 'refs/tags/v0.1.0^{commit}')" = "$(git rev-parse HEAD)" + cd build/release + sha256sum -c SHA256SUMS + - name: Prepare Homebrew test release notes + run: | + python3 - <<'PY' + from pathlib import Path + notes = Path('releases/v0.1.0.md').read_text() + notes = notes.replace('npm install -g @dittolive/cli\nnpm install -g @dittolive/cli@latest\n\n', '') + notes = notes.replace('The command is `dittosh`. npm installation requires Node.js 22 or newer;\nHomebrew manages Node.js 24.', 'The command is `dittosh`. Homebrew manages Node.js 24.') + notes = notes.replace('Linux x64/arm64, and Windows x64 (npm).', 'and Linux x64/arm64 for this Homebrew test.') + notes = notes.replace('Homebrew publication follows the GitHub/npm release in\nthe same workflow.', 'The formula downloads the archive attached to this GitHub release.\nMerge [tap PR #1](https://github.com/getditto/homebrew-tap/pull/1) before installing.\nPublishing @dittolive/cli to npm is deferred; no npm account is required.') + notes = notes.replace('Run `dittosh update` to update through the detected package manager.', 'Use `brew update && brew upgrade dittosh` for Homebrew updates.') + Path('build/release/notes.md').write_text(notes) + PY + - name: Publish GitHub assets without npm or tap credentials + env: + GH_TOKEN: ${{ github.token }} + run: | + # A completed release is immutable. Any retry must inspect it first. + existing=$(gh api 'repos/getditto/ditto-cli/releases?per_page=100' --jq '.[] | select(.tag_name == "v0.1.0") | .draft') + if [ "$existing" = false ]; then + echo '::error::v0.1.0 is already published; do not replace its assets.' + exit 1 + fi + if [ -z "$existing" ]; then + gh release create v0.1.0 --repo getditto/ditto-cli --verify-tag --draft \ + --title 'dittosh v0.1.0' --notes-file build/release/notes.md + fi + gh release upload v0.1.0 --repo getditto/ditto-cli --clobber \ + build/release/dittolive-cli-0.1.0.tgz build/release/SHA256SUMS build/release/dittosh.rb + gh release edit v0.1.0 --repo getditto/ditto-cli --draft=false \ + --notes-file build/release/notes.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c35ad6a..7c52191 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,6 +9,10 @@ on: description: "Build and test without publishing" type: boolean default: true + homebrew-test: + description: "Publish the reviewed v0.1.0 tag to GitHub for Homebrew testing only" + type: boolean + default: false permissions: contents: read @@ -20,11 +24,17 @@ concurrency: env: RELEASE_TAG: ${{ github.ref_name }} - DITTOSH_NO_UPDATE_CHECK: "1" jobs: + homebrew-test: + if: github.event_name == 'workflow_dispatch' && inputs.homebrew-test + uses: ./.github/workflows/homebrew-test-release.yml + permissions: + contents: write + secrets: inherit + prepare: - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v') && !inputs.homebrew-test runs-on: ubuntu-24.04 outputs: version: ${{ steps.validate.outputs.version }} From 682c366925a89de4cf3a6caa155039ba93db50b0 Mon Sep 17 00:00:00 2001 From: Aaron LaBeau <80424345+biozal@users.noreply.github.com> Date: Fri, 6 Nov 2026 10:22:28 -0600 Subject: [PATCH 2/2] Keep only the release test environment fix in PR 8 Remove the completed one-time Homebrew publishing workflow and its dispatch input. The final diff only removes the global update-check opt-out that broke two unit tests. Co-Authored-By: Codex --- .github/workflows/homebrew-test-release.yml | 149 -------------------- .github/workflows/release.yml | 13 +- 2 files changed, 1 insertion(+), 161 deletions(-) delete mode 100644 .github/workflows/homebrew-test-release.yml diff --git a/.github/workflows/homebrew-test-release.yml b/.github/workflows/homebrew-test-release.yml deleted file mode 100644 index c6cd928..0000000 --- a/.github/workflows/homebrew-test-release.yml +++ /dev/null @@ -1,149 +0,0 @@ -# One-time Homebrew bootstrap, pinned to the reviewed v0.1.0 source commit. -# Invoke through Release's homebrew-test input. No npm or cross-repository writes. -name: Homebrew v0.1.0 test release - -on: - workflow_call: - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ac9510e6ebc1118341a63cf8b8b398d41b88bb91 - fetch-depth: 0 - persist-credentials: false - - name: Verify the reviewed release tag - run: test "$(git rev-parse 'refs/tags/v0.1.0^{commit}')" = "$(git rev-parse HEAD)" - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: "24" - - run: npm ci - - run: npm run lint - - run: npm run typecheck - - run: npm run test:unit - - name: Build the stamped release archive - env: - OFFLINE_TOKEN: ${{ secrets.DQL_OFFLINE_LICENSE }} - DATABASE_ID: ${{ secrets.DITTO_DATABASE_ID }} - run: node --import tsx scripts/release.ts build v0.1.0 - - name: Point the formula at the GitHub release archive - run: | - node --import tsx --input-type=module <<'JS' - import fs from 'node:fs'; - import { digest, formula } from './scripts/release.ts'; - const archive = 'build/release/dittolive-cli-0.1.0.tgz'; - const url = 'https://github.com/getditto/ditto-cli/releases/download/v0.1.0/dittolive-cli-0.1.0.tgz'; - fs.writeFileSync('build/release/dittosh.rb', formula('0.1.0', digest(fs.readFileSync(archive)), url)); - JS - - uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 - with: - name: homebrew-v0.1.0 - path: | - build/release/*.tgz - build/release/SHA256SUMS - build/release/dittosh.rb - if-no-files-found: error - - quality: - needs: build - runs-on: macos-15 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ac9510e6ebc1118341a63cf8b8b398d41b88bb91 - persist-credentials: false - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: "24" - - run: npm ci - - name: Integration coverage and end-to-end tests - env: - OFFLINE_TOKEN: ${{ secrets.DQL_OFFLINE_LICENSE }} - DATABASE_ID: ${{ secrets.DITTO_DATABASE_ID }} - run: | - test -n "$OFFLINE_TOKEN" && test -n "$DATABASE_ID" - npm run coverage - npm run test:e2e - - install: - needs: build - strategy: - fail-fast: false - matrix: - os: [macos-15, ubuntu-24.04, ubuntu-24.04-arm] - runs-on: ${{ matrix.os }} - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ac9510e6ebc1118341a63cf8b8b398d41b88bb91 - persist-credentials: false - - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 - with: - node-version: "24" - - run: npm ci - - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 - with: - name: homebrew-v0.1.0 - path: build/release - - uses: Homebrew/actions/setup-homebrew@c262868c096d909fc278daa87ed9772967331ea3 - - name: Install and test the exact release archive with Homebrew - env: - HOMEBREW_NO_AUTO_UPDATE: "1" - HOMEBREW_NO_INSTALL_CLEANUP: "1" - run: node --import tsx scripts/release-smoke.ts homebrew v0.1.0 - - publish: - needs: [build, quality, install] - runs-on: ubuntu-24.04 - permissions: - contents: write - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ac9510e6ebc1118341a63cf8b8b398d41b88bb91 - fetch-depth: 0 - persist-credentials: false - - uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8 - with: - name: homebrew-v0.1.0 - path: build/release - - name: Verify archive checksum and unchanged tag - run: | - test "$(git rev-parse 'refs/tags/v0.1.0^{commit}')" = "$(git rev-parse HEAD)" - cd build/release - sha256sum -c SHA256SUMS - - name: Prepare Homebrew test release notes - run: | - python3 - <<'PY' - from pathlib import Path - notes = Path('releases/v0.1.0.md').read_text() - notes = notes.replace('npm install -g @dittolive/cli\nnpm install -g @dittolive/cli@latest\n\n', '') - notes = notes.replace('The command is `dittosh`. npm installation requires Node.js 22 or newer;\nHomebrew manages Node.js 24.', 'The command is `dittosh`. Homebrew manages Node.js 24.') - notes = notes.replace('Linux x64/arm64, and Windows x64 (npm).', 'and Linux x64/arm64 for this Homebrew test.') - notes = notes.replace('Homebrew publication follows the GitHub/npm release in\nthe same workflow.', 'The formula downloads the archive attached to this GitHub release.\nMerge [tap PR #1](https://github.com/getditto/homebrew-tap/pull/1) before installing.\nPublishing @dittolive/cli to npm is deferred; no npm account is required.') - notes = notes.replace('Run `dittosh update` to update through the detected package manager.', 'Use `brew update && brew upgrade dittosh` for Homebrew updates.') - Path('build/release/notes.md').write_text(notes) - PY - - name: Publish GitHub assets without npm or tap credentials - env: - GH_TOKEN: ${{ github.token }} - run: | - # A completed release is immutable. Any retry must inspect it first. - existing=$(gh api 'repos/getditto/ditto-cli/releases?per_page=100' --jq '.[] | select(.tag_name == "v0.1.0") | .draft') - if [ "$existing" = false ]; then - echo '::error::v0.1.0 is already published; do not replace its assets.' - exit 1 - fi - if [ -z "$existing" ]; then - gh release create v0.1.0 --repo getditto/ditto-cli --verify-tag --draft \ - --title 'dittosh v0.1.0' --notes-file build/release/notes.md - fi - gh release upload v0.1.0 --repo getditto/ditto-cli --clobber \ - build/release/dittolive-cli-0.1.0.tgz build/release/SHA256SUMS build/release/dittosh.rb - gh release edit v0.1.0 --repo getditto/ditto-cli --draft=false \ - --notes-file build/release/notes.md diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7c52191..3d1b4d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,10 +9,6 @@ on: description: "Build and test without publishing" type: boolean default: true - homebrew-test: - description: "Publish the reviewed v0.1.0 tag to GitHub for Homebrew testing only" - type: boolean - default: false permissions: contents: read @@ -26,15 +22,8 @@ env: RELEASE_TAG: ${{ github.ref_name }} jobs: - homebrew-test: - if: github.event_name == 'workflow_dispatch' && inputs.homebrew-test - uses: ./.github/workflows/homebrew-test-release.yml - permissions: - contents: write - secrets: inherit - prepare: - if: startsWith(github.ref, 'refs/tags/v') && !inputs.homebrew-test + if: startsWith(github.ref, 'refs/tags/v') runs-on: ubuntu-24.04 outputs: version: ${{ steps.validate.outputs.version }}