返回 CodeWhale
release.yml
根目录 / .github / workflows / release.yml
1 name: Release
2
3 on:
4 push:
5 tags: ['v*']
6 workflow_dispatch:
7 inputs:
8 version:
9 description: 'Release version, without v; dispatch from the matching existing vX.Y.Z tag ref'
10 required: true
11 type: string
12
13 concurrency:
14 group: release-${{ github.ref_name }}
15 cancel-in-progress: false
16
17 permissions:
18 contents: read
19
20 env:
21 CARGO_TERM_COLOR: always
22 CARGO_INCREMENTAL: 0
23 RUSTFLAGS: -Dwarnings
24
25 jobs:
26 resolve:
27 timeout-minutes: 10
28 runs-on: ubuntu-latest
29 outputs:
30 tag: ${{ steps.release.outputs.tag }}
31 sha: ${{ steps.release.outputs.sha }}
32 version: ${{ steps.release.outputs.version }}
33 steps:
34 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
35 with:
36 fetch-depth: 0
37 - name: Resolve release source
38 id: release
39 shell: bash
40 env:
41 INPUT_VERSION: ${{ inputs.version }}
42 run: |
43 set -euo pipefail
44
45 if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then
46 if ! [[ "${INPUT_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
47 echo "::error::Release version '${INPUT_VERSION}' must use X.Y.Z." >&2
48 exit 1
49 fi
50 tag="v${INPUT_VERSION}"
51 if [[ "${GITHUB_REF}" != "refs/tags/${tag}" ]]; then
52 echo "::error::Dispatch release.yml from --ref ${tag}, not ${GITHUB_REF}." >&2
53 exit 1
54 fi
55 else
56 tag="${GITHUB_REF_NAME}"
57 fi
58
59 if ! [[ "${tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
60 echo "::error::Release tag '${tag}' must use vX.Y.Z." >&2
61 exit 1
62 fi
63 if ! git rev-parse --verify "refs/tags/${tag}^{commit}" >/dev/null 2>&1; then
64 echo "::error::Release tag ${tag} does not exist. Create it from the frozen main commit before dispatching." >&2
65 exit 1
66 fi
67
68 sha="$(git rev-parse "refs/tags/${tag}^{commit}")"
69 event_sha="$(git rev-parse "${GITHUB_SHA}^{commit}")"
70 if [[ "${event_sha}" != "${sha}" ]]; then
71 echo "::error::Trigger SHA ${event_sha} does not match ${tag} at ${sha}; the tag moved after this run was created." >&2
72 exit 1
73 fi
74
75 {
76 echo "tag=${tag}"
77 echo "sha=${sha}"
78 echo "version=${tag#v}"
79 } >> "${GITHUB_OUTPUT}"
80 - name: Validate tagged release metadata
81 shell: bash
82 env:
83 SHA: ${{ steps.release.outputs.sha }}
84 TAG: ${{ steps.release.outputs.tag }}
85 run: |
86 set -euo pipefail
87 git checkout --detach "${SHA}"
88 expected="${TAG#v}"
89 workspace_version="$(grep -E '^version = "' Cargo.toml | head -n1 | sed -E 's/^version = "([^"]+)".*/\1/')"
90 npm_version="$(node -p "require('./npm/codewhale/package.json').version")"
91 binary_version="$(node -p "require('./npm/codewhale/package.json').codewhaleBinaryVersion")"
92 sdk_version="$(node -p "require('./npm/runtime-sdk/package.json').version")"
93 vscode_version="$(node -p "require('./extensions/vscode/package.json').version")"
94 for pair in \
95 "workspace:${workspace_version}" \
96 "npm:${npm_version}" \
97 "npm binary:${binary_version}" \
98 "runtime-sdk:${sdk_version}" \
99 "vscode:${vscode_version}"; do
100 label="${pair%%:*}"
101 actual="${pair#*:}"
102 if [[ "${actual}" != "${expected}" ]]; then
103 echo "::error::${label} version ${actual} does not match tag ${TAG}." >&2
104 exit 1
105 fi
106 done
107 ./scripts/release/check-versions.sh --require-dated-release
108 - name: Require release source on main
109 run: ./scripts/release/ensure-release-on-main.sh "${{ steps.release.outputs.sha }}"
110 - name: Refuse an existing public asset set
111 env:
112 GH_TOKEN: ${{ github.token }}
113 TAG: ${{ steps.release.outputs.tag }}
114 run: node scripts/release/ensure-release-assets-absent.js "${GITHUB_REPOSITORY}" "${TAG}"
115
116 parity:
117 timeout-minutes: 45
118 needs: resolve
119 runs-on: ubuntu-latest
120 steps:
121 # resolve already proved GITHUB_SHA equals the tag commit. Do not
122 # interpolate needs.resolve.outputs.sha into checkout or cache keys —
123 # CodeQL treats a *sha* ref as an untrusted checkout on workflow_dispatch
124 # (default-branch cache write).
125 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
126 - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # master 2026-07-18
127 with:
128 toolchain: stable
129 components: clippy, rustfmt
130 - uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
131 id: sccache
132 continue-on-error: true
133 - name: Enable sccache
134 if: steps.sccache.outcome == 'success'
135 shell: bash
136 run: |
137 {
138 echo "SCCACHE_GHA_ENABLED=true"
139 echo "RUSTC_WRAPPER=sccache"
140 echo "SCCACHE_IGNORE_SERVER_IO_ERROR=1"
141 } >> "${GITHUB_ENV}"
142 - name: Install Linux system dependencies
143 run: |
144 for i in 1 2 3 4 5; do
145 sudo apt-get update && break
146 echo "apt-get update failed (attempt $i); retrying in 15s"
147 sleep 15
148 done
149 sudo apt-get install -y libdbus-1-dev pkg-config
150 # Restore after the trusted lockfile is on disk. Key is OS + arch +
151 # explicit stable toolchain + rust-cache's Cargo.lock / rust-toolchain
152 # hash. Never interpolate github.event, github.ref, github.sha, or inputs.
153 - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
154 with:
155 cache-bin: false
156 prefix-key: v1-${{ runner.os }}-${{ runner.arch }}-stable
157 - name: Format check
158 run: cargo fmt --all -- --check
159 - name: Compile check
160 run: cargo check --workspace --all-targets --locked
161 - name: OHOS dependency graph
162 run: ./scripts/release/check-ohos-deps.sh
163 - name: Clippy
164 run: |
165 cargo clippy --workspace --all-targets --all-features --locked -- \
166 -D warnings \
167 -A clippy::uninlined_format_args \
168 -A clippy::too_many_arguments \
169 -A clippy::unnecessary_map_or \
170 -A clippy::collapsible_if \
171 -A clippy::assertions_on_constants
172 - name: Workspace tests
173 run: sh scripts/with-hermetic-test-home.sh cargo test --workspace --all-features --locked
174 env:
175 # Match the CI test lane: test threads get the same stack the product
176 # gives itself (main.rs CODEWHALE_MAIN_STACK_BYTES). See the note in
177 # ci.yml's "Run tests" step. Without it this gate runs the deep
178 # engine/runtime futures on a stack that never ships.
179 RUST_MIN_STACK: '16777216'
180 - name: Protocol schema parity
181 run: sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-protocol --test parity_protocol --locked
182 - name: State persistence parity
183 run: sh scripts/with-hermetic-test-home.sh cargo test -p codewhale-state --test parity_state --locked
184 - name: Lockfile drift guard
185 run: git diff --exit-code -- Cargo.lock
186
187 artifacts:
188 needs: [parity, resolve]
189 if: ${{ !cancelled() && needs.resolve.result == 'success' && needs.parity.result == 'success' }}
190 uses: ./.github/workflows/release-artifacts.yml
191 with:
192 source_sha: ${{ needs.resolve.outputs.sha }}
193 version: ${{ needs.resolve.outputs.version }}
194 retention_days: 14
195
196 docker-build:
197 needs: [artifacts, resolve]
198 if: ${{ !cancelled() && needs.artifacts.result == 'success' }}
199 name: Docker ${{ matrix.platform }}
200 timeout-minutes: 60
201 strategy:
202 fail-fast: false
203 matrix:
204 include:
205 - runner: ubuntu-latest
206 platform: linux/amd64
207 architecture: amd64
208 cli_artifact: codewhale-linux-x64
209 shim_artifact: codew-linux-x64
210 - runner: ubuntu-24.04-arm
211 platform: linux/arm64
212 architecture: arm64
213 cli_artifact: codewhale-linux-arm64
214 shim_artifact: codew-linux-arm64
215 runs-on: ${{ matrix.runner }}
216 permissions:
217 contents: read
218 packages: write
219 steps:
220 - name: Checkout release infrastructure
221 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
222 with:
223 ref: ${{ needs.resolve.outputs.sha }}
224 path: infra
225 - name: Download Codewhale release binary
226 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
227 with:
228 name: ${{ matrix.cli_artifact }}
229 path: docker-context/bin
230 - name: Download codew release alias
231 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
232 with:
233 name: ${{ matrix.shim_artifact }}
234 path: docker-context/bin
235 - name: Verify native release bytes
236 shell: bash
237 env:
238 CLI_ARTIFACT: ${{ matrix.cli_artifact }}
239 SHIM_ARTIFACT: ${{ matrix.shim_artifact }}
240 run: |
241 set -euo pipefail
242 mv -- "docker-context/bin/${CLI_ARTIFACT}" docker-context/bin/codewhale
243 mv -- "docker-context/bin/${SHIM_ARTIFACT}" docker-context/bin/codew
244 chmod 0755 docker-context/bin/codewhale docker-context/bin/codew
245 cmp docker-context/bin/codewhale docker-context/bin/codew
246 docker-context/bin/codewhale --version
247 docker-context/bin/codew --version
248 - name: Set up Docker Buildx
249 uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4
250 - name: Log in to GitHub Container Registry
251 uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
252 with:
253 registry: ghcr.io
254 username: ${{ github.repository_owner }}
255 password: ${{ secrets.GITHUB_TOKEN }}
256 - name: Normalize image name
257 id: image
258 shell: bash
259 run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
260 - name: Extract image labels
261 id: meta
262 uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
263 with:
264 images: |
265 ${{ steps.image.outputs.name }}
266 tags: |
267 type=raw,value=${{ needs.resolve.outputs.version }}
268 - name: Revalidate release tag before container upload
269 env:
270 EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
271 TAG: ${{ needs.resolve.outputs.tag }}
272 run: |
273 ./infra/scripts/release/verify-remote-tag.sh \
274 "https://github.com/${GITHUB_REPOSITORY}.git" \
275 "${TAG}" \
276 "${EXPECTED_SHA}"
277 - name: Assemble and push native image by digest
278 id: build
279 uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
280 env:
281 DOCKER_BUILD_RECORD_UPLOAD: false
282 DOCKER_BUILD_SUMMARY: false
283 with:
284 context: docker-context
285 file: infra/packaging/docker/Dockerfile.release
286 platforms: ${{ matrix.platform }}
287 provenance: mode=max
288 sbom: true
289 labels: ${{ steps.meta.outputs.labels }}
290 outputs: type=image,name=${{ steps.image.outputs.name }},push-by-digest=true,name-canonical=true,push=true
291 - name: Smoke native image digest
292 shell: bash
293 env:
294 IMAGE: ${{ steps.image.outputs.name }}@${{ steps.build.outputs.digest }}
295 run: |
296 set -euo pipefail
297 docker pull "${IMAGE}"
298 docker run --rm --entrypoint codewhale "${IMAGE}" --version
299 docker run --rm --entrypoint codew "${IMAGE}" --version
300 - name: Export image digest
301 shell: bash
302 env:
303 DIGEST: ${{ steps.build.outputs.digest }}
304 run: |
305 set -euo pipefail
306 if ! [[ "${DIGEST}" =~ ^sha256:[0-9a-f]{64}$ ]]; then
307 echo "Unexpected image digest: ${DIGEST}" >&2
308 exit 1
309 fi
310 mkdir -p digests
311 touch "digests/${DIGEST#sha256:}"
312 - name: Upload image digest
313 uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
314 with:
315 name: docker-digest-${{ matrix.architecture }}
316 path: digests/*
317 if-no-files-found: error
318 retention-days: 1
319 overwrite: true
320
321 docker:
322 timeout-minutes: 30
323 needs: [docker-build, resolve]
324 if: ${{ !cancelled() && needs.docker-build.result == 'success' }}
325 runs-on: ubuntu-latest
326 permissions:
327 contents: read
328 packages: write
329 steps:
330 - name: Checkout release infrastructure
331 uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
332 with:
333 ref: ${{ needs.resolve.outputs.sha }}
334 path: infra
335 - name: Download native image digests
336 uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
337 with:
338 path: digests
339 pattern: docker-digest-*
340 merge-multiple: true
341 - name: Set up Docker Buildx
342 uses: docker/setup-buildx-action@594f3bf4285d9ea8dc53c9a0c9c4092420091003 # v4
343 - name: Log in to GitHub Container Registry
344 uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4
345 with:
346 registry: ghcr.io
347 username: ${{ github.repository_owner }}
348 password: ${{ secrets.GITHUB_TOKEN }}
349 - name: Normalize image name
350 id: image
351 shell: bash
352 run: echo "name=ghcr.io/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
353 - name: Extract metadata
354 id: meta
355 uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
356 with:
357 images: |
358 ${{ steps.image.outputs.name }}
359 tags: |
360 type=semver,pattern={{version}}
361 type=semver,pattern={{major}}.{{minor}}
362 type=semver,pattern=v{{major}}
363 type=ref,event=tag
364 type=semver,pattern={{version}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
365 type=semver,pattern={{major}}.{{minor}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
366 type=semver,pattern=v{{major}},value=${{ needs.resolve.outputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }}
367 type=raw,value=${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
368 type=raw,value=v${{ inputs.version }},enable=${{ github.event_name == 'workflow_dispatch' }}
369 type=raw,value=latest
370 - name: Revalidate release tag before container publish
371 env:
372 EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
373 TAG: ${{ needs.resolve.outputs.tag }}
374 run: |
375 ./infra/scripts/release/verify-remote-tag.sh \
376 "https://github.com/${GITHUB_REPOSITORY}.git" \
377 "${TAG}" \
378 "${EXPECTED_SHA}"
379 - name: Publish multi-architecture manifest
380 shell: bash
381 env:
382 IMAGE: ${{ steps.image.outputs.name }}
383 TAGS: ${{ steps.meta.outputs.tags }}
384 run: |
385 set -euo pipefail
386 mapfile -t digest_files < <(find digests -maxdepth 1 -type f -printf '%f\n' | sort)
387 if [[ "${#digest_files[@]}" -ne 2 ]]; then
388 echo "Expected exactly two native image digests; found ${#digest_files[@]}." >&2
389 exit 1
390 fi
391
392 sources=()
393 for digest in "${digest_files[@]}"; do
394 if ! [[ "${digest}" =~ ^[0-9a-f]{64}$ ]]; then
395 echo "Unexpected image digest file: ${digest}" >&2
396 exit 1
397 fi
398 sources+=("${IMAGE}@sha256:${digest}")
399 done
400
401 tag_args=()
402 while IFS= read -r tag; do
403 [[ -n "${tag}" ]] && tag_args+=(--tag "${tag}")
404 done <<< "${TAGS}"
405 if [[ "${#tag_args[@]}" -eq 0 ]]; then
406 echo "No container tags were generated." >&2
407 exit 1
408 fi
409
410 docker buildx imagetools create "${tag_args[@]}" "${sources[@]}"
411 - name: Verify and smoke published container
412 shell: bash
413 env:
414 IMAGE: ${{ steps.image.outputs.name }}:${{ needs.resolve.outputs.tag }}
415 run: |
416 set -euo pipefail
417 docker buildx imagetools inspect "${IMAGE}"
418 raw_manifest="$(docker buildx imagetools inspect --raw "${IMAGE}")"
419 jq -e \
420 '[.manifests[] | select(.platform.os == "linux") | "linux/\(.platform.architecture)"] | unique | sort == ["linux/amd64", "linux/arm64"]' \
421 <<< "${raw_manifest}"
422 docker pull "${IMAGE}"
423 docker run --rm --entrypoint codewhale "${IMAGE}" --version
424 docker run --rm --entrypoint codew "${IMAGE}" --version
425
426 release:
427 timeout-minutes: 30
428 needs: [artifacts, docker, resolve]
429 if: ${{ !cancelled() && needs.artifacts.result == 'success' && needs.docker.result == 'success' }}
430 runs-on: ubuntu-latest
431 permissions:
432 contents: write
433 steps:
434 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
435 with:
436 ref: ${{ needs.resolve.outputs.sha }}
437 path: repo
438 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
439 with:
440 node-version: 20
441 package-manager-cache: false
442 - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
443 with:
444 name: codewhale-release-assets
445 path: artifacts
446 - name: Revalidate exact authoritative asset set
447 run: node repo/scripts/release/assemble-release-assets.js --verify artifacts
448 - name: Generate release body from CHANGELOG
449 shell: bash
450 run: |
451 ./repo/scripts/release/generate-release-body.sh \
452 "${{ needs.resolve.outputs.tag }}" repo/CHANGELOG.md > release-body.md
453 - name: Revalidate release tag before GitHub Release write
454 env:
455 EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
456 TAG: ${{ needs.resolve.outputs.tag }}
457 run: |
458 ./repo/scripts/release/verify-remote-tag.sh \
459 "https://github.com/${GITHUB_REPOSITORY}.git" \
460 "${TAG}" \
461 "${EXPECTED_SHA}"
462 - name: Reconfirm public asset set is still empty
463 env:
464 GH_TOKEN: ${{ github.token }}
465 TAG: ${{ needs.resolve.outputs.tag }}
466 run: node repo/scripts/release/ensure-release-assets-absent.js "${GITHUB_REPOSITORY}" "${TAG}"
467 - uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3
468 with:
469 tag_name: ${{ needs.resolve.outputs.tag }}
470 files: artifacts/*
471 prerelease: false
472 body_path: release-body.md
473 overwrite_files: false
474 fail_on_unmatched_files: true
475
476 npm:
477 timeout-minutes: 20
478 needs: [release, resolve]
479 if: ${{ !cancelled() && needs.release.result == 'success' }}
480 runs-on: ubuntu-latest
481 permissions:
482 contents: read
483 id-token: write
484 steps:
485 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
486 with:
487 ref: ${{ needs.resolve.outputs.sha }}
488 fetch-depth: 0
489 - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
490 with:
491 node-version: 24
492 registry-url: https://registry.npmjs.org
493 package-manager-cache: false
494 - name: Pin OIDC-capable npm CLI
495 run: npm install --global npm@12.0.2
496 - name: Revalidate release tag before npm publish
497 env:
498 EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
499 TAG: ${{ needs.resolve.outputs.tag }}
500 run: |
501 ./scripts/release/verify-remote-tag.sh \
502 "https://github.com/${GITHUB_REPOSITORY}.git" \
503 "${TAG}" \
504 "${EXPECTED_SHA}"
505 - name: Revalidate public release assets
506 env:
507 GH_TOKEN: ${{ github.token }}
508 run: ./scripts/release/verify-release-assets.sh "${{ needs.resolve.outputs.version }}"
509 - name: Test npm wrapper
510 working-directory: npm/codewhale
511 run: npm test
512 - name: Publish npm wrapper with trusted publishing
513 working-directory: npm/codewhale
514 env:
515 # npm runs prepublishOnly in this step; that guard revalidates the
516 # public GitHub Release and therefore needs the same read token as
517 # the explicit asset gate above.
518 GH_TOKEN: ${{ github.token }}
519 run: npm publish --access public
520
521 homebrew:
522 timeout-minutes: 20
523 needs: [release, resolve]
524 if: ${{ !cancelled() && needs.release.result == 'success' }}
525 runs-on: ubuntu-latest
526 permissions:
527 contents: read
528 steps:
529 - name: Check Homebrew tap token
530 id: homebrew-token
531 env:
532 TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
533 run: |
534 if [[ -z "${TOKEN:-}" ]]; then
535 echo "No Homebrew tap token configured; skipping tap update."
536 echo "available=false" >> "${GITHUB_OUTPUT}"
537 else
538 echo "available=true" >> "${GITHUB_OUTPUT}"
539 fi
540 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
541 if: steps.homebrew-token.outputs.available == 'true'
542 with:
543 ref: ${{ needs.resolve.outputs.sha }}
544 - name: Download checksum manifest
545 if: steps.homebrew-token.outputs.available == 'true'
546 env:
547 GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
548 run: |
549 gh release download "${{ needs.resolve.outputs.tag }}" \
550 --repo "${{ github.repository }}" \
551 --pattern 'codewhale-artifacts-sha256.txt' \
552 --dir /tmp
553 - name: Revalidate release tag before Homebrew tap write
554 if: steps.homebrew-token.outputs.available == 'true'
555 env:
556 EXPECTED_SHA: ${{ needs.resolve.outputs.sha }}
557 TAG: ${{ needs.resolve.outputs.tag }}
558 run: |
559 ./scripts/release/verify-remote-tag.sh \
560 "https://github.com/${GITHUB_REPOSITORY}.git" \
561 "${TAG}" \
562 "${EXPECTED_SHA}"
563 - name: Update Homebrew tap
564 if: steps.homebrew-token.outputs.available == 'true'
565 env:
566 TAG: ${{ needs.resolve.outputs.tag }}
567 MANIFEST: /tmp/codewhale-artifacts-sha256.txt
568 TAP_REPO: Hmbown/homebrew-deepseek-tui
569 TOKEN: ${{ secrets.HOMEBREW_TAP_PAT || secrets.RELEASE_TAG_PAT }}
570 run: bash .github/scripts/update-homebrew-tap.sh
571
571 lines YAML