Skip to content

Fix non-default-base issue auto-close and misleading link diagnostics (#1895) #3137

Fix non-default-base issue auto-close and misleading link diagnostics (#1895)

Fix non-default-base issue auto-close and misleading link diagnostics (#1895) #3137

Workflow file for this run

name: Checks and release
on:
push:
branches:
- main
pull_request:
types: [opened, synchronize, reopened]
# Do not use workflow-level path filters here. The detect-changes job must
# run for every PR update so non-code-only changes still produce completed
# checks while expensive jobs are skipped by job-level conditions.
# Manual release support - consolidated here to work with npm trusted publishing
# npm only allows ONE workflow file as trusted publisher, so all publishing
# must go through this workflow (release.yml)
workflow_dispatch:
inputs:
release_mode:
description: 'Manual release mode'
required: true
type: choice
default: 'instant'
options:
- instant
- changeset-pr
bump_type:
description: 'Manual release type'
required: true
type: choice
options:
- patch
- minor
- major
description:
description: 'Manual release description (optional)'
required: false
type: string
# Concurrency: Only one workflow run per branch at a time
# - For main branch (releases): cancel older runs to prevent blocking newer releases
# When multiple commits are pushed quickly, we want the latest to release, not wait
# - For PR branches: queue runs to avoid cancelling checks on force-pushes
# IMPORTANT: Docker jobs use !cancelled() instead of always() to ensure they respect
# concurrency cancellation. Using always() prevents workflow cancellation entirely.
# See: docs/case-studies/issue-1274/README.md and docs/case-studies/issue-1278/README.md
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
env: # Force Node.js 24 for actions without native support. See issue #1417.
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# === DETECT CHANGES ===
detect-changes:
runs-on: ubuntu-latest
outputs:
mjs-changed: ${{ steps.changes.outputs.mjs }}
package-changed: ${{ steps.changes.outputs.package }}
docs-changed: ${{ steps.changes.outputs.docs }}
workflow-changed: ${{ steps.changes.outputs.workflow }}
docker-changed: ${{ steps.changes.outputs.docker }}
any-code-changed: ${{ steps.changes.outputs.code }}
helm-changed: ${{ steps.changes.outputs.helm }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Detect changes
id: changes
env:
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_EVENT_ACTION: ${{ github.event.action }}
GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
GITHUB_BEFORE_SHA: ${{ github.event.before }}
GITHUB_AFTER_SHA: ${{ github.event.after }}
run: node scripts/detect-code-changes.mjs
# === VERSION CHANGE CHECK ===
# Prohibit manual version changes in package.json - versions should only be changed by CI/CD
# Only runs when code or package.json changed (Issue #1436: skip for unrelated file changes)
version-check:
name: Check for Manual Version Changes
runs-on: ubuntu-latest
needs: [detect-changes]
if: github.event_name == 'pull_request' && (needs.detect-changes.outputs.any-code-changed == 'true' || needs.detect-changes.outputs.package-changed == 'true')
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Check for version changes in package.json
env:
GITHUB_HEAD_REF: ${{ github.head_ref }}
GITHUB_BASE_REF: ${{ github.base_ref }}
run: node scripts/check-version.mjs
# === CHANGESET CHECK - only runs on PRs with code changes ===
# Docs-only PRs (./docs folder, markdown files) don't require changesets
changeset-check:
name: Check for Changesets
runs-on: ubuntu-latest
needs: [detect-changes]
if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-code-changed == 'true'
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '24.x'
- name: Install dependencies
run: node scripts/npm-install-with-retry.mjs install
- name: Check for changesets
env:
# Pass PR context to the validation script
GITHUB_BASE_REF: ${{ github.base_ref }}
GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
# Skip changeset check for automated version PRs
if [[ "${{ github.head_ref }}" == "changeset-release/"* ]]; then
echo "Skipping changeset check for automated release PR"
exit 0
fi
# Run changeset validation script
# This validates that exactly ONE changeset was ADDED by this PR
# Pre-existing changesets from other merged PRs are ignored
node scripts/validate-changeset.mjs
# === FAST CHECKS (FAIL FAST) ===
# These checks run quickly (~7-21 seconds each) and should pass before long-running tests
# === COMPILATION & SYNTAX CHECKS ===
test-compilation:
runs-on: ubuntu-latest
needs: [detect-changes, changeset-check]
# Run if: push event, OR changeset-check succeeded, OR changeset-check was skipped (docs-only PR)
if: always() && (github.event_name == 'push' || needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') && (needs.detect-changes.outputs.any-code-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true')
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 24.x
- name: Test solve.mjs compilation
run: |
echo "Testing solve.mjs compilation..."
timeout 30s node --check src/solve.mjs
echo "solve.mjs compiles successfully"
- name: Test hive.mjs compilation
run: |
echo "Testing hive.mjs compilation..."
timeout 30s node --check src/hive.mjs
echo "hive.mjs compiles successfully"
- name: Check Node.js syntax for all .mjs files
run: bash scripts/check-mjs-syntax.sh
# === ESLINT CODE QUALITY CHECK ===
# Lint runs independently of changeset-check - it's a fast check that should always run
# See: docs/case-studies/issue-1023 for why this dependency was removed
# IMPORTANT: ESLint now includes max-lines rule (1500 lines) to synchronize with check-file-line-limits
# See docs/case-studies/issue-1141 for why fresh merge simulation is critical
lint:
runs-on: ubuntu-latest
needs: [detect-changes]
if: needs.detect-changes.outputs.mjs-changed == 'true' || needs.detect-changes.outputs.docs-changed == 'true' || needs.detect-changes.outputs.package-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true'
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
# For PRs, fetch enough history to merge with base branch
fetch-depth: 0
- name: Simulate fresh merge with base branch (PR only)
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: bash scripts/simulate-fresh-merge.sh
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 24.x
- name: Install dependencies
run: node scripts/npm-install-with-retry.mjs ci
- name: Run Prettier format check
run: |
echo "Running Prettier format check..."
npm run format:check
echo "Prettier format check passed"
- name: Run ESLint
run: |
echo "Running ESLint code quality checks..."
npm run lint
echo "ESLint checks passed"
- name: Run code duplication check
run: |
echo "Running jscpd code duplication detection..."
npm run check:duplication
echo "Code duplication check passed"
# === FILE LINE LIMIT CHECK ===
# IMPORTANT: This check must validate the ACTUAL merge result, not a stale merge preview.
# See docs/case-studies/issue-1141 for why this is critical.
check-file-line-limits:
runs-on: ubuntu-latest
needs: [detect-changes, changeset-check]
# Run if: push event, OR changeset-check succeeded, OR changeset-check was skipped (docs-only PR)
if: always() && (github.event_name == 'push' || needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') && (needs.detect-changes.outputs.mjs-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true')
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
# For PRs, fetch enough history to merge with base branch
fetch-depth: 0
- name: Simulate fresh merge with base branch (PR only)
if: github.event_name == 'pull_request'
env:
BASE_REF: ${{ github.base_ref }}
run: bash scripts/simulate-fresh-merge.sh
- name: Check file line limits (.mjs files and release.yml)
run: bash scripts/check-file-line-limits.sh
# === LONG-RUNNING CHECKS ===
# These checks take significant time (30s to 4+ minutes) and only run after all fast checks pass
# === UNIT TESTS ===
test-suites:
runs-on: ubuntu-latest
needs: [detect-changes, changeset-check, test-compilation, lint, check-file-line-limits]
if: |
always() &&
!cancelled() &&
!contains(needs.*.result, 'failure') &&
(github.event_name == 'push' || needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') &&
needs.test-compilation.result == 'success' &&
(needs.lint.result == 'success' || needs.lint.result == 'skipped') &&
(needs.check-file-line-limits.result == 'success' || needs.check-file-line-limits.result == 'skipped') &&
(needs.detect-changes.outputs.any-code-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true')
steps:
- uses: actions/checkout@v5
- name: Use Node.js 20.x
uses: actions/setup-node@v5
with:
node-version: 24.x
- name: Install dependencies
run: node scripts/npm-install-with-retry.mjs install
- name: Pre-install use-m packages (issue #1724)
run: node scripts/preinstall-use-m-packages.mjs
- name: Run default test suite
run: npm test
- name: Run GitHub integration test suite
env:
GITHUB_TOKEN: ${{ secrets.TEST_GITHUB_USER_TOKEN || secrets.GITHUB_TOKEN }}
TEST_GITHUB_USERNAME: ${{ secrets.TEST_GITHUB_USERNAME }}
TEST_GITHUB_USER_TOKEN: ${{ secrets.TEST_GITHUB_USER_TOKEN }}
run: node scripts/run-tests.mjs --suite github-integration
# === EXECUTION TESTS ===
test-execution:
runs-on: ubuntu-latest
needs: [detect-changes, changeset-check, test-compilation, lint, check-file-line-limits]
if: |
always() &&
!cancelled() &&
!contains(needs.*.result, 'failure') &&
(github.event_name == 'push' || needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') &&
needs.test-compilation.result == 'success' &&
(needs.lint.result == 'success' || needs.lint.result == 'skipped') &&
(needs.check-file-line-limits.result == 'success' || needs.check-file-line-limits.result == 'skipped') &&
(needs.detect-changes.outputs.any-code-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true')
steps:
- uses: actions/checkout@v5
- name: Use Node.js 20.x
uses: actions/setup-node@v5
with:
node-version: 24.x
- name: Install dependencies
run: node scripts/npm-install-with-retry.mjs install
- name: Pre-install use-m packages (issue #1724)
run: node scripts/preinstall-use-m-packages.mjs
- name: Test solve.mjs execution
run: |
echo "Testing solve.mjs basic execution..."
timeout 10s ./src/solve.mjs --help || echo "Help command completed"
echo "solve.mjs executes without critical errors"
timeout 10s ./src/solve.mjs --version || true
- name: Verify log files contain version and command (Issue #517)
run: bash scripts/verify-log-file-contents.sh
- name: Test hive.mjs execution
run: |
echo "Testing hive.mjs basic execution..."
timeout 10s ./src/hive.mjs --help || echo "Help command completed"
echo "hive.mjs executes without critical errors"
timeout 10s ./src/hive.mjs --version || true
- name: Test telegram-bot.mjs execution
run: |
echo "Testing telegram-bot.mjs basic execution..."
timeout 10s ./src/telegram-bot.mjs --help || echo "Help command completed"
echo "telegram-bot.mjs executes without critical errors"
echo ""
echo "Testing telegram-bot.mjs --dry-run with issue #487 command..."
timeout 10s ./src/telegram-bot.mjs \
--token "test_token_123" \
--allowed-chats "(-1002975819706 -1002861722681)" \
--no-hive \
--solve-overrides $'( \n --auto-continue\n --attach-logs\n --verbose\n --no-tool-check\n)' \
--dry-run
echo "Issue #487 command passes with --dry-run"
- name: Test memory-check.mjs execution
run: |
./src/memory-check.mjs --help
./src/memory-check.mjs --min-memory 10 --min-disk-space 100 --json
- name: Test global command installation
run: bash scripts/test-global-commands.sh
- name: Test hive dry-run with solve integration
run: |
echo "Testing hive dry-run mode with solve command integration..."
timeout 30s ./src/hive.mjs https://github.com/test/repo --dry-run --skip-claude-check --once --max-issues 1 2>&1 | tee hive_dry_run.log || true
if grep -q "solve.*--dry-run.*--skip-claude-check" hive_dry_run.log; then
echo "hive correctly passes --dry-run and --skip-claude-check flags to solve command"
else
echo "Could not verify flag propagation in dry-run mode (may be due to no issues found)"
fi
echo ""
echo "Testing solve.mjs with --dry-run and --skip-claude-check flags..."
timeout 10s ./src/solve.mjs https://github.com/test/repo/issues/1 --dry-run --skip-claude-check 2>&1 | head -20 || true
echo "solve.mjs accepts --dry-run and --skip-claude-check flags"
- name: Test --auto-fork option
run: bash scripts/test-auto-fork-option.sh
# === MEMORY CHECKS - LINUX ===
memory-check-linux:
runs-on: ubuntu-latest
needs: [detect-changes, changeset-check, test-compilation, lint, check-file-line-limits]
if: |
always() &&
!cancelled() &&
!contains(needs.*.result, 'failure') &&
(github.event_name == 'push' || needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') &&
needs.test-compilation.result == 'success' &&
(needs.lint.result == 'success' || needs.lint.result == 'skipped') &&
(needs.check-file-line-limits.result == 'success' || needs.check-file-line-limits.result == 'skipped') &&
(needs.detect-changes.outputs.any-code-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true')
steps:
- uses: actions/checkout@v5
- name: Use Node.js 20.x
uses: actions/setup-node@v5
with:
node-version: 24.x
- name: Install dependencies
run: node scripts/npm-install-with-retry.mjs install
- name: System info
run: |
echo "=== System Information ==="
uname -a
echo ""
echo "=== Memory Information ==="
free -h
echo ""
echo "=== Disk Information ==="
df -h
echo ""
echo "=== CPU Information ==="
lscpu | head -20
- name: Run memory-check tests
run: |
chmod +x tests/test-memory-check.mjs
node tests/test-memory-check.mjs
- name: Test memory-check with various thresholds
run: |
echo "Testing with low thresholds (should pass)..."
./src/memory-check.mjs --min-memory 10 --min-disk-space 100 --json
echo ""
echo "Testing verbose output..."
./src/memory-check.mjs --min-memory 10 --min-disk-space 100
echo ""
echo "Testing quiet mode..."
./src/memory-check.mjs --min-memory 10 --min-disk-space 100 --quiet --json
- name: Test memory-check failure conditions
run: |
echo "Testing with impossible memory requirement (should fail)..."
if ./src/memory-check.mjs --min-memory 999999 --exit-on-failure --quiet --json; then
echo "ERROR: Should have failed with impossible memory requirement"
exit 1
else
echo "Correctly failed with impossible memory requirement"
fi
echo ""
echo "Testing with impossible disk requirement (should fail)..."
if ./src/memory-check.mjs --min-disk-space 999999999 --exit-on-failure --quiet --json; then
echo "ERROR: Should have failed with impossible disk requirement"
exit 1
else
echo "Correctly failed with impossible disk requirement"
fi
# === DOCUMENTATION VALIDATION (FAST) ===
validate-docs:
runs-on: ubuntu-latest
needs: [detect-changes, changeset-check]
# Run if: push event, OR changeset-check succeeded, OR changeset-check was skipped (docs-only PR)
if: always() && (github.event_name == 'push' || needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') && needs.detect-changes.outputs.docs-changed == 'true'
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Use Node.js 20.x
uses: actions/setup-node@v5
with:
node-version: 24.x
- name: Validate documentation files
env:
GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }}
GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
GITHUB_BEFORE_SHA: ${{ github.event.before }}
GITHUB_AFTER_SHA: ${{ github.event.after }}
run: |
node tests/docs-validation.mjs
node tests/test-docs-language-sync.mjs
# === DOCKER PR CHECK ===
docker-pr-check:
runs-on: ubuntu-latest
needs: [detect-changes, changeset-check, test-compilation, lint, check-file-line-limits]
# Run if: changeset-check succeeded OR was skipped (docs-only PR), and required checks passed
if: |
always() &&
!cancelled() &&
!contains(needs.*.result, 'failure') &&
github.event_name == 'pull_request' &&
(needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') &&
(needs.test-compilation.result == 'success' || needs.test-compilation.result == 'skipped') &&
(needs.lint.result == 'success' || needs.lint.result == 'skipped') &&
(needs.check-file-line-limits.result == 'success' || needs.check-file-line-limits.result == 'skipped') &&
(needs.detect-changes.outputs.docker-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true')
env:
REGISTRY: docker.io
IMAGE_NAME: konard/hive-mind
DIND_IMAGE_NAME: konard/hive-mind-dind
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: 24.x
- name: Verify Docker release-order contract
run: |
echo "Statically verifying: release Docker publish waits for npm availability"
echo "and passes the exact published version into Docker builds."
node tests/test-docker-release-order.mjs
- name: Free up disk space
run: node scripts/free-disk-space.mjs
- name: Show Box base image versions
run: |
BOX_VERSION=$(grep '^FROM konard/box:' Dockerfile | sed 's/FROM konard\/box://')
BOX_DIND_VERSION=$(grep '^FROM konard/box-dind:' Dockerfile.dind | sed 's/FROM konard\/box-dind://')
echo "Box base image version: konard/box:${BOX_VERSION}"
echo "Box DinD base image version: konard/box-dind:${BOX_DIND_VERSION}"
echo "This pinned version ensures stable, reproducible builds."
- name: Build Docker images
# PR builds install @link-assistant/hive-mind@latest (the currently
# published version on npm, which may pre-date this PR). The Dockerfile
# tolerates a missing configure-claude bin in that case. Release builds
# install the exact version just published and enforce the bin strictly.
run: |
BOX_VERSION=$(grep '^FROM konard/box:' Dockerfile | sed 's/FROM konard\/box://')
BOX_DIND_VERSION=$(grep '^FROM konard/box-dind:' Dockerfile.dind | sed 's/FROM konard\/box-dind://')
echo "Building Docker image from konard/box:${BOX_VERSION} base..."
echo "Note: General-purpose tools are inherited from pinned konard/box:${BOX_VERSION}"
echo "This image adds AI-specific tools on top of the Box base."
echo "Installing @link-assistant/hive-mind@latest; configure-claude may not yet be present in the published package."
echo ""
# Multi-platform builds (amd64+arm64) are tested in docker-publish during release.
# PR checks only validate amd64 using standard docker build (not buildx) so the
# image is loaded into the Docker daemon for container verification.
docker build --progress=plain -t ${{ env.IMAGE_NAME }}:test . 2>&1 | tee build-output.log
echo ""
echo "Building Docker-in-Docker image from konard/box-dind:${BOX_DIND_VERSION} base..."
docker build --progress=plain -f Dockerfile.dind -t ${{ env.DIND_IMAGE_NAME }}:test . 2>&1 | tee build-dind-output.log
echo ""
echo "Docker images built successfully"
docker images | grep -E "${{ env.IMAGE_NAME }}|${{ env.DIND_IMAGE_NAME }}|REPOSITORY"
- name: Verify build completed without errors
run: |
echo "Checking build logs for critical errors..."
if grep -E 'unbound variable' build-output.log build-dind-output.log; then
echo "ERROR: Unbound variable error detected in Docker build"
grep -E 'unbound variable' build-output.log build-dind-output.log || true
exit 1
fi
echo "Build log check completed"
- name: Verify system & development tools in running containers
run: |
BOX_VERSION=$(grep '^FROM konard/box:' Dockerfile | sed 's/FROM konard\/box://')
BOX_DIND_VERSION=$(grep '^FROM konard/box-dind:' Dockerfile.dind | sed 's/FROM konard\/box-dind://')
echo "=== Verifying hive-mind Docker image ==="
echo "Base: konard/box:${BOX_VERSION} (pinned) + AI-specific tools"
echo ""
# Run verification script inside container (mounts script as read-only volume)
# Verifies box user setup (/home/box access), dev tools, AI tools,
# and configure-claude (tolerantly for PR builds).
docker run --rm \
-v "$(pwd)/scripts/verify-docker-image.sh:/verify-docker-image.sh:ro" \
${{ env.IMAGE_NAME }}:test \
bash /verify-docker-image.sh
echo ""
echo "=== Verifying hive-mind Docker-in-Docker image ==="
echo "Base: konard/box-dind:${BOX_DIND_VERSION} (pinned) + AI-specific tools"
docker run --rm --privileged \
-v "$(pwd)/scripts/verify-docker-image.sh:/verify-docker-image.sh:ro" \
${{ env.DIND_IMAGE_NAME }}:test \
bash /verify-docker-image.sh
bash scripts/verify-dind-exec-defaults.sh "${{ env.DIND_IMAGE_NAME }}:test"
echo ""
echo "All system, development, and nested Docker verification checks passed!"
# === HELM PR CHECK (FAST) ===
helm-pr-check:
runs-on: ubuntu-latest
needs: [detect-changes, changeset-check]
# Only runs when helm chart, package.json, or workflow files changed (Issue #1436)
if: always() && github.event_name == 'pull_request' && (needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') && (needs.detect-changes.outputs.helm-changed == 'true' || needs.detect-changes.outputs.package-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true')
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: v3.14.0
- name: Get package version
id: package-version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Package version: $VERSION"
- name: Lint Helm chart
run: |
echo "Linting Helm chart..."
helm lint helm/hive-mind
echo "Helm chart validation passed"
- name: Verify Chart.yaml structure
run: bash scripts/verify-chart-yaml.sh
# === RELEASE - only runs on main after tests pass ===
release:
name: Release
needs: [detect-changes, lint, test-suites, test-execution, memory-check-linux]
if: always() && github.ref == 'refs/heads/main' && github.event_name == 'push' && !contains(needs.*.result, 'failure') && (needs.lint.result == 'success' || needs.lint.result == 'skipped') && (needs.test-suites.result == 'success' || needs.test-suites.result == 'skipped') && (needs.test-execution.result == 'success' || needs.test-execution.result == 'skipped') && (needs.memory-check-linux.result == 'success' || needs.memory-check-linux.result == 'skipped') && (needs.detect-changes.outputs.any-code-changed == 'true' || needs.detect-changes.outputs.docker-changed == 'true' || needs.detect-changes.outputs.workflow-changed == 'true')
runs-on: ubuntu-latest
outputs:
published: ${{ steps.publish.outputs.published }}
published_version: ${{ steps.publish.outputs.published_version }}
# Permissions required for npm OIDC trusted publishing
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '24.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: node scripts/npm-install-with-retry.mjs install
- name: Update npm for OIDC trusted publishing
run: node scripts/setup-npm.mjs
- name: Check for changesets
id: check_changesets
run: |
# Count changeset files (excluding README.md and config.json)
CHANGESET_COUNT=$(find .changeset -name "*.md" ! -name "README.md" | wc -l)
echo "Found $CHANGESET_COUNT changeset file(s)"
echo "has_changesets=$([[ $CHANGESET_COUNT -gt 0 ]] && echo 'true' || echo 'false')" >> $GITHUB_OUTPUT
echo "changeset_count=$CHANGESET_COUNT" >> $GITHUB_OUTPUT
- name: Harmonize changeset bump types
if: steps.check_changesets.outputs.has_changesets == 'true' && steps.check_changesets.outputs.changeset_count > 1
run: |
echo "Multiple changesets detected, harmonizing bump types..."
node scripts/merge-changesets.mjs
- name: Version packages and commit to main
if: steps.check_changesets.outputs.has_changesets == 'true'
id: version
run: node scripts/version-and-commit.mjs --mode changeset
- name: Publish to npm
# Run if version was committed OR if a previous attempt already committed (for re-runs)
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
id: publish_npm
run: node scripts/publish-to-npm.mjs --should-pull
- name: Set publish outputs
# This step explicitly sets the outputs using shell (more reliable than Node.js appendFileSync)
# It runs after npm publish and reads the script's outputs to pass them along
id: publish
if: steps.publish_npm.outputs.published == 'true' || steps.publish_npm.outputs.already_published == 'true'
run: |
echo "published=true" >> $GITHUB_OUTPUT
echo "published_version=${{ steps.publish_npm.outputs.published_version }}" >> $GITHUB_OUTPUT
echo "Docker publish will be triggered with version: ${{ steps.publish_npm.outputs.published_version }}"
- name: Create GitHub Release
if: steps.publish.outputs.published == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}"
- name: Format GitHub release notes
if: steps.publish.outputs.published == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}"
- name: Post-publish - Upload Source Maps to Sentry
if: steps.publish.outputs.published == 'true'
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
run: |
echo "Uploading source maps to Sentry for published packages..."
chmod +x scripts/upload-sourcemaps.mjs
node scripts/upload-sourcemaps.mjs
# === DOCKER PUBLISH ===
# Multi-platform builds using native runners (no QEMU). See: docs/case-studies/issue-982/
# ARM64 may be slow: actions/runner-images#11790, actions/partner-runner-images#101
# See also: docs/case-studies/issue-998/README.md
docker-publish:
name: Docker Publish (${{ matrix.platform }})
needs: [release]
# !cancelled() allows concurrency cancellation. See: docs/case-studies/issue-1278/
if: "!cancelled() && needs.release.result == 'success' && needs.release.outputs.published == 'true'"
# Timeout 60 min: arm64 can take 26+ min. See: docs/case-studies/issue-1415/README.md
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
cache_suffix: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm # Native ARM64, no QEMU. See: docs/case-studies/issue-998/
cache_suffix: arm64
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
env:
REGISTRY: docker.io
IMAGE_NAME: konard/hive-mind
outputs:
digest-amd64: ${{ steps.build.outputs.digest }}
digest-arm64: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Free up disk space
run: node scripts/free-disk-space.mjs
- name: Wait for NPM package availability
run: node scripts/wait-for-npm.mjs --release-version "${{ needs.release.outputs.published_version }}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
# Set version label to actual release version, not Git ref "main" (issue #1419)
labels: |
org.opencontainers.image.version=${{ needs.release.outputs.published_version }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
HIVE_MIND_VERSION=${{ needs.release.outputs.published_version }}
# Registry cache for faster parallel export. See: docs/case-studies/issue-1415/README.md
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache-${{ matrix.cache_suffix }}
cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache-${{ matrix.cache_suffix }},mode=max,image-manifest=true,ignore-error=true
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
echo "Digest for ${{ matrix.platform }}: $digest"
- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: hive-mind-digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Merge multi-platform manifests after all platform builds complete
docker-publish-merge:
name: Docker Publish (Merge)
needs: [release, docker-publish]
# Use !cancelled() instead of always() to allow concurrency cancellation.
# See: docs/case-studies/issue-1278/README.md for why this change was necessary.
if: "!cancelled() && needs.release.result == 'success' && needs.docker-publish.result == 'success' && needs.release.outputs.published == 'true'"
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
IMAGE_NAME: konard/hive-mind
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: hive-mind-digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ needs.release.outputs.published_version }}
type=raw,value=latest
# Set version label to actual release version, not Git ref "main" (issue #1419)
labels: |
org.opencontainers.image.version=${{ needs.release.outputs.published_version }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
echo "Creating multi-platform manifest..."
echo "Tags: ${{ steps.meta.outputs.tags }}"
echo "Digests:"
ls -la
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Inspect image
run: |
echo "Inspecting multi-platform image..."
docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ needs.release.outputs.published_version }}
- name: Verify published image
run: |
echo "Docker image published successfully"
echo "Image: ${{ env.IMAGE_NAME }}"
echo "Tags: ${{ steps.meta.outputs.tags }}"
docker-publish-dind:
name: Docker Publish DinD (${{ matrix.platform }})
needs: [release]
if: "!cancelled() && needs.release.result == 'success' && needs.release.outputs.published == 'true'"
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
cache_suffix: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
cache_suffix: arm64
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
env:
REGISTRY: docker.io
IMAGE_NAME: konard/hive-mind-dind
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Free up disk space
run: node scripts/free-disk-space.mjs
- name: Wait for NPM package availability
run: node scripts/wait-for-npm.mjs --release-version "${{ needs.release.outputs.published_version }}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
labels: |
org.opencontainers.image.version=${{ needs.release.outputs.published_version }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile.dind
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
HIVE_MIND_VERSION=${{ needs.release.outputs.published_version }}
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache-dind-${{ matrix.cache_suffix }}
cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache-dind-${{ matrix.cache_suffix }},mode=max,image-manifest=true,ignore-error=true
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
echo "Digest for ${{ matrix.platform }}: $digest"
- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: hive-mind-dind-digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
docker-publish-dind-merge:
name: Docker Publish DinD (Merge)
needs: [release, docker-publish-dind]
if: "!cancelled() && needs.release.result == 'success' && needs.docker-publish-dind.result == 'success' && needs.release.outputs.published == 'true'"
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
IMAGE_NAME: konard/hive-mind-dind
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: hive-mind-dind-digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ needs.release.outputs.published_version }}
type=raw,value=latest
labels: |
org.opencontainers.image.version=${{ needs.release.outputs.published_version }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
echo "Creating multi-platform manifest..."
echo "Tags: ${{ steps.meta.outputs.tags }}"
echo "Digests:"
ls -la
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Inspect image
run: docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ needs.release.outputs.published_version }}
- name: Verify published image
run: |
echo "Docker DinD image published successfully"
echo "Image: ${{ env.IMAGE_NAME }}"
echo "Tags: ${{ steps.meta.outputs.tags }}"
# === HELM CHART RELEASE ===
helm-release:
name: Helm Release
needs: [release, docker-publish-merge, docker-publish-dind-merge]
# Use !cancelled() instead of always() to allow concurrency cancellation.
# See: docs/case-studies/issue-1278/README.md for why this change was necessary.
if: "!cancelled() && needs.release.result == 'success' && needs.docker-publish-merge.result == 'success' && needs.docker-publish-dind-merge.result == 'success' && needs.release.outputs.published == 'true'"
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
pages: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: v3.14.0
- name: Release Helm chart
run: node scripts/helm-release.mjs --release-version "${{ needs.release.outputs.published_version }}"
- name: Upload chart to release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.release.outputs.published_version }}
files: .helm-packages/*.tgz
# === MANUAL INSTANT RELEASE ===
instant-release:
name: Instant Release
if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant'
runs-on: ubuntu-latest
outputs:
published: ${{ steps.publish.outputs.published }}
published_version: ${{ steps.publish.outputs.published_version }}
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '24.x'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
run: node scripts/npm-install-with-retry.mjs install
- name: Update npm for OIDC trusted publishing
run: node scripts/setup-npm.mjs
- name: Version packages and commit to main
id: version
run: node scripts/version-and-commit.mjs --mode instant --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}"
- name: Publish to npm
if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true'
id: publish_npm
run: node scripts/publish-to-npm.mjs
- name: Set publish outputs
# This step explicitly sets the outputs using shell (more reliable than Node.js appendFileSync)
id: publish
if: steps.publish_npm.outputs.published == 'true' || steps.publish_npm.outputs.already_published == 'true'
run: |
echo "published=true" >> $GITHUB_OUTPUT
echo "published_version=${{ steps.publish_npm.outputs.published_version }}" >> $GITHUB_OUTPUT
echo "Docker publish will be triggered with version: ${{ steps.publish_npm.outputs.published_version }}"
- name: Create GitHub Release
if: steps.publish.outputs.published == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}"
- name: Format GitHub release notes
if: steps.publish.outputs.published == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: node scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}"
# === DOCKER PUBLISH FOR INSTANT RELEASE ===
# Same multi-platform setup as docker-publish above (native runners, no QEMU).
# See: docs/case-studies/issue-982/README.md and docs/case-studies/issue-998/README.md
docker-publish-instant:
name: Docker Publish Instant (${{ matrix.platform }})
needs: [instant-release]
# Use !cancelled() instead of always() to allow concurrency cancellation.
# See: docs/case-studies/issue-1278/README.md for why this change was necessary.
if: "!cancelled() && needs.instant-release.result == 'success' && needs.instant-release.outputs.published == 'true'"
# Timeout 60 min: arm64 can take 26+ min. See: docs/case-studies/issue-1415/README.md
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
cache_suffix: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm # Native ARM64, no QEMU. See: docs/case-studies/issue-998/
cache_suffix: arm64
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
env:
REGISTRY: docker.io
IMAGE_NAME: konard/hive-mind
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Free up disk space
run: node scripts/free-disk-space.mjs
- name: Wait for NPM package availability
run: node scripts/wait-for-npm.mjs --release-version "${{ needs.instant-release.outputs.published_version }}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
# Set version label to actual release version, not Git ref "main" (issue #1419)
labels: |
org.opencontainers.image.version=${{ needs.instant-release.outputs.published_version }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
HIVE_MIND_VERSION=${{ needs.instant-release.outputs.published_version }}
# Registry cache for faster parallel export. See: docs/case-studies/issue-1415/README.md
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache-${{ matrix.cache_suffix }}
cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache-${{ matrix.cache_suffix }},mode=max,image-manifest=true,ignore-error=true
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
echo "Digest for ${{ matrix.platform }}: $digest"
- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: hive-mind-instant-digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
# Merge multi-platform manifests after all platform builds complete (instant release)
docker-publish-instant-merge:
name: Docker Publish Instant (Merge)
needs: [instant-release, docker-publish-instant]
# Use !cancelled() instead of always() to allow concurrency cancellation.
# See: docs/case-studies/issue-1278/README.md for why this change was necessary.
if: "!cancelled() && needs.instant-release.result == 'success' && needs.docker-publish-instant.result == 'success' && needs.instant-release.outputs.published == 'true'"
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
IMAGE_NAME: konard/hive-mind
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: hive-mind-instant-digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ needs.instant-release.outputs.published_version }}
type=raw,value=latest
# Set version label to actual release version, not Git ref "main" (issue #1419)
labels: |
org.opencontainers.image.version=${{ needs.instant-release.outputs.published_version }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
echo "Creating multi-platform manifest..."
echo "Tags: ${{ steps.meta.outputs.tags }}"
echo "Digests:"
ls -la
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Inspect image
run: |
echo "Inspecting multi-platform image..."
docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ needs.instant-release.outputs.published_version }}
- name: Verify published image
run: |
echo "Docker image published successfully"
echo "Image: ${{ env.IMAGE_NAME }}"
echo "Tags: ${{ steps.meta.outputs.tags }}"
docker-publish-dind-instant:
name: Docker Publish DinD Instant (${{ matrix.platform }})
needs: [instant-release]
if: "!cancelled() && needs.instant-release.result == 'success' && needs.instant-release.outputs.published == 'true'"
timeout-minutes: 60
strategy:
fail-fast: false
matrix:
include:
- platform: linux/amd64
runner: ubuntu-latest
cache_suffix: amd64
- platform: linux/arm64
runner: ubuntu-24.04-arm
cache_suffix: arm64
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write
env:
REGISTRY: docker.io
IMAGE_NAME: konard/hive-mind-dind
steps:
- name: Checkout repository
uses: actions/checkout@v5
- name: Free up disk space
run: node scripts/free-disk-space.mjs
- name: Wait for NPM package availability
run: node scripts/wait-for-npm.mjs --release-version "${{ needs.instant-release.outputs.published_version }}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
labels: |
org.opencontainers.image.version=${{ needs.instant-release.outputs.published_version }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
file: ./Dockerfile.dind
platforms: ${{ matrix.platform }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
HIVE_MIND_VERSION=${{ needs.instant-release.outputs.published_version }}
cache-from: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache-dind-${{ matrix.cache_suffix }}
cache-to: type=registry,ref=${{ env.IMAGE_NAME }}:buildcache-dind-${{ matrix.cache_suffix }},mode=max,image-manifest=true,ignore-error=true
outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
- name: Export digest
run: |
mkdir -p /tmp/digests
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
echo "Digest for ${{ matrix.platform }}: $digest"
- name: Upload digest
uses: actions/upload-artifact@v7
with:
name: hive-mind-dind-instant-digests-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1
docker-publish-dind-instant-merge:
name: Docker Publish DinD Instant (Merge)
needs: [instant-release, docker-publish-dind-instant]
if: "!cancelled() && needs.instant-release.result == 'success' && needs.docker-publish-dind-instant.result == 'success' && needs.instant-release.outputs.published == 'true'"
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
env:
IMAGE_NAME: konard/hive-mind-dind
steps:
- name: Download digests
uses: actions/download-artifact@v8
with:
path: /tmp/digests
pattern: hive-mind-dind-instant-digests-*
merge-multiple: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ env.IMAGE_NAME }}
tags: |
type=raw,value=${{ needs.instant-release.outputs.published_version }}
type=raw,value=latest
labels: |
org.opencontainers.image.version=${{ needs.instant-release.outputs.published_version }}
- name: Create manifest list and push
working-directory: /tmp/digests
run: |
echo "Creating multi-platform manifest..."
echo "Tags: ${{ steps.meta.outputs.tags }}"
echo "Digests:"
ls -la
docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf '${{ env.IMAGE_NAME }}@sha256:%s ' *)
- name: Inspect image
run: docker buildx imagetools inspect ${{ env.IMAGE_NAME }}:${{ needs.instant-release.outputs.published_version }}
- name: Verify published image
run: |
echo "Docker DinD image published successfully"
echo "Image: ${{ env.IMAGE_NAME }}"
echo "Tags: ${{ steps.meta.outputs.tags }}"
# === HELM CHART RELEASE FOR INSTANT RELEASE ===
helm-release-instant:
name: Helm Release (Instant)
needs: [instant-release, docker-publish-instant-merge, docker-publish-dind-instant-merge]
# Use !cancelled() instead of always() to allow concurrency cancellation.
# See: docs/case-studies/issue-1278/README.md for why this change was necessary.
if: "!cancelled() && needs.instant-release.result == 'success' && needs.docker-publish-instant-merge.result == 'success' && needs.docker-publish-dind-instant-merge.result == 'success' && needs.instant-release.outputs.published == 'true'"
runs-on: ubuntu-latest
permissions:
contents: write
packages: write
pages: write
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@v4
with:
version: v3.14.0
- name: Release Helm chart
run: node scripts/helm-release.mjs --release-version "${{ needs.instant-release.outputs.published_version }}"
- name: Upload chart to release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.instant-release.outputs.published_version }}
files: .helm-packages/*.tgz
# === MANUAL CHANGESET PR ===
changeset-pr:
name: Create Changeset PR
if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'changeset-pr'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '24.x'
- name: Install dependencies
run: node scripts/npm-install-with-retry.mjs install
- name: Create changeset file
run: node scripts/create-manual-changeset.mjs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}"
- name: Format changeset with Prettier
run: |
npx prettier --write ".changeset/*.md" || true
echo "Formatted changeset files"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v8
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: 'chore: add changeset for manual ${{ github.event.inputs.bump_type }} release'
branch: changeset-manual-release-${{ github.run_id }}
delete-branch: true
title: 'chore: manual ${{ github.event.inputs.bump_type }} release'
body: |
## Manual Release Request
This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** release.
### Release Details
- **Type:** ${{ github.event.inputs.bump_type }}
- **Description:** ${{ github.event.inputs.description || 'Manual release' }}
- **Triggered by:** @${{ github.actor }}
### Next Steps
1. Review the changeset in this PR
2. Merge this PR to main
3. The automated release workflow will create a version PR
4. Merge the version PR to publish to npm and create a GitHub release