diff --git a/.github/actions/setup_node_environment/action.yml b/.github/actions/setup_node_environment/action.yml new file mode 100644 index 000000000..081702cae --- /dev/null +++ b/.github/actions/setup_node_environment/action.yml @@ -0,0 +1,16 @@ +name: 'Setup Node Environment' +description: 'Sets up pnpm, Node.js, and installs dependencies' +runs: + using: 'composite' + steps: + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node.js + uses: useblacksmith/setup-node@v5 + with: + node-version-file: .nvmrc + + - name: Install dependencies + shell: bash + run: pnpm install --frozen-lockfile diff --git a/.github/workflows/build_ensnode.yml b/.github/workflows/build_ensnode.yml index 586ec35a2..4c081e7ce 100644 --- a/.github/workflows/build_ensnode.yml +++ b/.github/workflows/build_ensnode.yml @@ -1,4 +1,4 @@ -name: "Build: ENSNode" +name: "[Deprecated] Build: ENSNode" on: workflow_dispatch: diff --git a/.github/workflows/release-npm-rc.yml b/.github/workflows/release-npm-rc.yml deleted file mode 100644 index 841035ca0..000000000 --- a/.github/workflows/release-npm-rc.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: Release NPM RC - -on: - workflow_dispatch: - -concurrency: ${{ github.workflow }}-${{ github.ref }} - -jobs: - publish-rc: - name: Publish Release Candidate Packages to NPM - if: github.repository == 'namehash/ensnode' - runs-on: blacksmith-4vcpu-ubuntu-2204 - permissions: - contents: read - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - fetch-depth: 1 - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: useblacksmith/setup-node@v5 - with: - node-version-file: .nvmrc - - - name: Install dependencies - run: pnpm install --frozen-lockfile - - - name: Enter pre-release mode - run: pnpm changeset pre enter rc - - - name: Version packages - run: pnpm changeset version - - - name: Build packages - run: pnpm packages:prepublish - - - name: Publish to NPM with rc tag - run: pnpm changeset publish --tag rc - env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Summary - run: | - echo "✅ Release candidate packages published to NPM with 'rc' tag" - echo "" - echo "Install with: pnpm install @ensnode/package-name@rc" - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bc7e89872..e2dbb4e29 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,37 @@ +# Full Release Workflow (@latest) +# +# This workflow creates stable releases of ENSNode packages and Docker images. +# It is triggered automatically when the Changesets Release PR is merged to main. +# +# How it works: +# 1. When PRs with changesets are merged to main, the Changesets bot creates/updates a Release PR +# 2. When the Release PR is merged, this workflow: +# - Publishes NPM packages with @latest tag +# - Builds and publishes Docker images to GitHub Container Registry with @latest tag +# - Creates a GitHub release with version tags and release notes +# - Sends Slack notification +# +# Published artifacts: +# - NPM packages: @ensnode/* packages published to npm registry with @latest tag +# - Docker images: ensindexer, ensadmin, ensapi, ensrainbow published to ghcr.io with @latest tag +# - GitHub Release: Created with version tag (e.g., v1.2.3) and autogenerated release notes +# +# Version management: +# - All ENSNode packages use "fixed" versioning - they all advance to the same version +# - The version is determined by the changesets included in the Release PR +# - Version bump type (major/minor/patch) is based on the changeset severity levels +# +# Important notes: +# - Only Full Releases are considered stable for production use +# - Only NameHash Labs ensnode team members can merge the Release PR +# - This workflow does NOT run on regular commits to main (see release_snapshot.yml for that) +# - GitHub releases and tags are only created for Full Releases +# +# Documentation: https://ensnode.io/docs/contributing/releases#full-release + name: Release on: - workflow_dispatch: push: branches: - main @@ -27,16 +57,7 @@ jobs: with: fetch-depth: 1 - - name: Setup pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: useblacksmith/setup-node@v5 - with: - node-version-file: .nvmrc - - - name: Install dependencies - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup_node_environment - name: Create Release Pull Request or Publish to npm uses: changesets/action@v1.4.10 diff --git a/.github/workflows/release_preview.yml b/.github/workflows/release_preview.yml new file mode 100644 index 000000000..42cc199fb --- /dev/null +++ b/.github/workflows/release_preview.yml @@ -0,0 +1,431 @@ +# Release Preview Workflow (@preview-*) +# +# This workflow creates preview releases for testing features before they are merged to main. +# It must be triggered manually via the GitHub Actions UI. +# +# Requirements: +# - The branch must have an open PR targeting main +# - Cannot be run on the main branch +# +# How to trigger: +# 1. Navigate to Actions > Release Preview in GitHub +# 2. Click "Run workflow" and select: +# - Branch: The branch you want to create a preview release from (must have an open PR) +# - Publish target: +# - npm-only: NPM packages only +# - npm-and-lambdas: NPM packages + Lambda functions +# - npm-and-ghcr: NPM packages + Docker images +# - all: NPM packages + Lambda functions + Docker images +# - Custom tag suffix (optional): Custom tag suffix instead of branch name +# +# What it does: +# 1. Validates the branch (cannot run on main) and verifies an open PR exists +# 2. Generates a sanitized tag from the branch name or custom tag suffix (e.g., @preview-feat-add-api-route) +# 3. Versions packages using changesets in snapshot mode +# 4. Publishes NPM packages with @preview-* dist-tag +# 5. Optionally builds and publishes Lambda functions as GitHub Actions artifacts +# 6. Optionally builds and publishes Docker images with preview-* tag +# 7. Posts/updates a comment on the PR with installation instructions +# +# Published artifacts: +# - NPM packages: Published with @preview-{branch-name} or @preview-{custom-suffix} tag +# - Lambda functions (optional): Published as GitHub Actions artifacts +# - Docker images (optional): Published with preview-{branch-name} or preview-{custom-suffix} tag +# - No GitHub releases or tags are created +# +# Use cases: +# - Testing PR features before merging to main +# - Collaborating on development work +# - Validating fixes for specific issues +# - QA testing of work-in-progress features +# +# Important notes: +# - Preview releases require an open PR targeting main +# - Preview releases are NOT stable and should not be used in production +# - They are ephemeral and don't modify the repository (no commits/tags in git) +# - Only authorized NameHash team members can trigger preview releases +# - Each preview release will update the PR comment with installation instructions +# - Installation: npm install @ensnode/package-name@preview-branch-name +# - Cleanup: Use npm dist-tag rm to manually remove preview tags when no longer needed +# +# Documentation: https://ensnode.io/docs/contributing/releases#preview-release + +name: Release Preview (@preview) + +on: + workflow_dispatch: + inputs: + custom_suffix: + description: 'Optional custom suffix to append to the tag assigned to all assets published in the release preview. If not defined, defaults to the name of the branch associated with the release preview.' + type: string + required: false + publish_target: + description: 'Where should the release preview be published?' + type: choice + required: true + default: 'all' + options: + - npm-only + - npm-and-lambdas + - npm-and-ghcr + - all + +concurrency: + group: prerelease-${{ github.ref_name }} + cancel-in-progress: true + +jobs: + validate-and-prepare: + name: Validate Branch & Prepare + runs-on: blacksmith-4vcpu-ubuntu-2204 + if: github.repository == 'namehash/ensnode' + outputs: + branch-suffix: ${{ steps.prepare.outputs.branch-suffix }} + dist-tag: ${{ steps.prepare.outputs.dist-tag }} + snapshot-tag: ${{ steps.prepare.outputs.snapshot-tag }} + docker-tag-base: ${{ steps.prepare.outputs.docker-tag-base }} + commit-sha: ${{ steps.prepare.outputs.commit-sha }} + pr-number: ${{ steps.validate.outputs.pr-number }} + pr-title: ${{ steps.validate.outputs.pr-title }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Validate branch and PR + id: validate + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if [[ "${{ github.ref_name }}" == "main" ]]; then + echo "❌ Cannot run prerelease workflow on main branch" + echo "Use the snapshot workflow instead for main branch previews" + exit 1 + fi + echo "✅ Branch validation passed: ${{ github.ref_name }}" + + # Require an open PR for this branch + PR_DATA=$(gh pr list --state=open --head="${{ github.ref_name }}" --base=main --json number,title --limit 1) + + if [[ "$PR_DATA" == "[]" || -z "$PR_DATA" ]]; then + echo "❌ No open PR found for branch '${{ github.ref_name }}'" + echo "Preview releases require an open PR targeting main." + echo "Please create a PR for this branch before running a preview release." + exit 1 + fi + + PR_NUMBER=$(echo "$PR_DATA" | jq -r '.[0].number') + PR_TITLE=$(echo "$PR_DATA" | jq -r '.[0].title') + echo "pr-number=${PR_NUMBER}" >> $GITHUB_OUTPUT + echo "pr-title=${PR_TITLE}" >> $GITHUB_OUTPUT + echo "✅ Found PR #${PR_NUMBER}: ${PR_TITLE}" + + - name: Prepare tags and identifiers + id: prepare + run: | + # Get short commit SHA + COMMIT_SHA=$(git rev-parse --short HEAD) + + # Sanitize branch name for use in versions and tags + if [[ -n "${{ inputs.custom_suffix }}" ]]; then + BRANCH_SUFFIX="${{ inputs.custom_suffix }}" + else + BRANCH_SUFFIX="${{ github.ref_name }}" + fi + # Replace / with - + BRANCH_SUFFIX="${BRANCH_SUFFIX//\//-}" + # Replace special characters with - + BRANCH_SUFFIX="${BRANCH_SUFFIX//[^a-zA-Z0-9-]/-}" + # Convert to lowercase + BRANCH_SUFFIX=$(echo "$BRANCH_SUFFIX" | tr '[:upper:]' '[:lower:]') + # Remove consecutive dashes (repeat until no more changes) + while [[ "$BRANCH_SUFFIX" =~ --+ ]]; do + BRANCH_SUFFIX="${BRANCH_SUFFIX//--/-}" + done + # Truncate to 30 characters + BRANCH_SUFFIX="${BRANCH_SUFFIX:0:30}" + # Remove trailing dashes + BRANCH_SUFFIX="${BRANCH_SUFFIX%-}" + # Remove leading dashes + BRANCH_SUFFIX="${BRANCH_SUFFIX#-}" + + # Create dist-tag (NPM doesn't allow dots in tags) + DIST_TAG="preview-${BRANCH_SUFFIX}" + + # Snapshot tag for changesets (used in version string) + SNAPSHOT_TAG="preview-${BRANCH_SUFFIX}" + + # Docker tag base (no version, just branch) + DOCKER_TAG_BASE="preview-${BRANCH_SUFFIX}" + + echo "branch-suffix=${BRANCH_SUFFIX}" >> $GITHUB_OUTPUT + echo "dist-tag=${DIST_TAG}" >> $GITHUB_OUTPUT + echo "snapshot-tag=${SNAPSHOT_TAG}" >> $GITHUB_OUTPUT + echo "docker-tag-base=${DOCKER_TAG_BASE}" >> $GITHUB_OUTPUT + echo "commit-sha=${COMMIT_SHA}" >> $GITHUB_OUTPUT + + echo "🏷️ NPM dist-tag: ${DIST_TAG}" + echo "📸 Snapshot tag: ${SNAPSHOT_TAG}" + echo "🐳 Docker tag base: ${DOCKER_TAG_BASE}" + echo "📝 Commit SHA: ${COMMIT_SHA}" + + build-and-publish-npm: + name: Build & Publish Release Preview Packages to NPM + runs-on: blacksmith-4vcpu-ubuntu-2204 + needs: validate-and-prepare + if: inputs.publish_target == 'npm-only' || inputs.publish_target == 'npm-and-lambdas' || inputs.publish_target == 'npm-and-ghcr' || inputs.publish_target == 'all' + permissions: + contents: read + id-token: write + outputs: + published-version: ${{ steps.publish.outputs.published-version }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: ./.github/actions/setup_node_environment + + - name: Build packages + run: pnpm packages:prepublish + + - name: Version packages with changesets (snapshot) + run: | + # Use changesets to create snapshot versions + # The snapshot tag will be used as part of the version string + pnpm changeset version --snapshot ${{ needs.validate-and-prepare.outputs.snapshot-tag }} + + echo "📦 Packages versioned with snapshot tag: ${{ needs.validate-and-prepare.outputs.snapshot-tag }}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish release preview packages to NPM with changesets + id: publish + run: | + # Publish with changesets using snapshot mode + # --no-git-tag: Don't create git tags for preview releases + # --snapshot: Publish as snapshot/preview release + # --tag: Use our custom dist-tag + pnpm changeset publish --no-git-tag --snapshot --tag ${{ needs.validate-and-prepare.outputs.dist-tag }} + + # Extract published version from package.json + PUBLISHED_VERSION=$(node -p "require('./packages/ensnode-sdk/package.json').version") + echo "published-version=${PUBLISHED_VERSION}" >> $GITHUB_OUTPUT + + echo "✅ Published release preview packages to NPM with version: ${PUBLISHED_VERSION}" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + build-and-publish-ghcr: + name: Build & Publish Docker Images to GHCR + runs-on: blacksmith-4vcpu-ubuntu-2204 + needs: [validate-and-prepare, build-and-publish-npm] + if: inputs.publish_target == 'npm-and-ghcr' || inputs.publish_target == 'all' + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + app: [ensindexer, ensadmin, ensapi, ensrainbow] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Build & Publish Docker Image + uses: ./.github/actions/build_docker_image + with: + image: ghcr.io/${{ github.repository }}/${{ matrix.app }} + dockerfile: apps/${{ matrix.app }}/Dockerfile + registry_user: ${{ github.actor }} + registry_token: ${{ secrets.GITHUB_TOKEN }} + tags: | + type=raw,value=${{ needs.validate-and-prepare.outputs.docker-tag-base }} + type=raw,value=${{ needs.validate-and-prepare.outputs.docker-tag-base }}-${{ needs.validate-and-prepare.outputs.commit-sha }} + type=sha + + build-and-publish-lambdas: + name: Build & Publish Preview Lambdas + runs-on: blacksmith-4vcpu-ubuntu-2204 + needs: [validate-and-prepare, build-and-publish-npm] + if: inputs.publish_target == 'npm-and-lambdas' || inputs.publish_target == 'all' + strategy: + fail-fast: false + matrix: + lambda: [fallback-ensapi] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Build and Publish Lambda + uses: ./.github/actions/build_and_publish_lambda + with: + name: ${{ matrix.lambda }} + version: ${{ needs.validate-and-prepare.outputs.dist-tag }} + + update-pr-comment: + name: Update PR Comment + runs-on: blacksmith-4vcpu-ubuntu-2204 + needs: [validate-and-prepare, build-and-publish-npm, build-and-publish-ghcr, build-and-publish-lambdas] + if: always() && needs.build-and-publish-npm.result == 'success' + permissions: + contents: read + pull-requests: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Generate PR comment body + id: comment + run: | + # Build timestamp for display + BUILD_TIME=$(date -u +"%Y-%m-%d %H:%M:%S UTC") + + # Start building comment + COMMENT_BODY="## 🚀 Preview Packages - \`${{ github.ref_name }}\` + + " + + # NPM section - always present since both options include NPM + if [[ "${{ needs.build-and-publish-npm.result }}" == "success" ]]; then + COMMENT_BODY+="**NPM Packages:** + \`\`\`bash + # Install latest preview for this branch + pnpm add @ensnode/datasources@${{ needs.validate-and-prepare.outputs.dist-tag }} + pnpm add @ensnode/ensnode-react@${{ needs.validate-and-prepare.outputs.dist-tag }} + pnpm add @ensnode/ensrainbow-sdk@${{ needs.validate-and-prepare.outputs.dist-tag }} + pnpm add @ensnode/ensnode-schema@${{ needs.validate-and-prepare.outputs.dist-tag }} + pnpm add @ensnode/ensnode-sdk@${{ needs.validate-and-prepare.outputs.dist-tag }} + pnpm add @ensnode/ponder-subgraph@${{ needs.validate-and-prepare.outputs.dist-tag }} + pnpm add @ensnode/ponder-metadata@${{ needs.validate-and-prepare.outputs.dist-tag }} + pnpm add @ensnode/ens-referrals@${{ needs.validate-and-prepare.outputs.dist-tag }} + + # Or install specific version + pnpm add @ensnode/ensnode-sdk@${{ needs.build-and-publish-npm.outputs.published-version }} + \`\`\` + + " + fi + + # Lambdas section - only if lambdas were requested + if [[ "${{ inputs.publish_target }}" == "npm-and-lambdas" || "${{ inputs.publish_target }}" == "all" ]]; then + if [[ "${{ needs.build-and-publish-lambdas.result }}" == "success" ]]; then + COMMENT_BODY+="**Lambda Functions:** + \`\`\` + Published as GitHub Actions artifacts: + - fallback-ensapi-${{ needs.validate-and-prepare.outputs.dist-tag }} + + Download from: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + \`\`\` + + " + else + COMMENT_BODY+="**Lambda Functions:** ❌ Build failed - see [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + " + fi + fi + + # GHCR section - only if ghcr was requested + if [[ "${{ inputs.publish_target }}" == "npm-and-ghcr" || "${{ inputs.publish_target }}" == "all" ]]; then + if [[ "${{ needs.build-and-publish-ghcr.result }}" == "success" ]]; then + COMMENT_BODY+="**Docker Images:** + \`\`\`bash + docker pull ghcr.io/namehash/ensnode/ensindexer:${{ needs.validate-and-prepare.outputs.docker-tag-base }}-${{ needs.validate-and-prepare.outputs.commit-sha }} + docker pull ghcr.io/namehash/ensnode/ensadmin:${{ needs.validate-and-prepare.outputs.docker-tag-base }}-${{ needs.validate-and-prepare.outputs.commit-sha }} + docker pull ghcr.io/namehash/ensnode/ensapi:${{ needs.validate-and-prepare.outputs.docker-tag-base }}-${{ needs.validate-and-prepare.outputs.commit-sha }} + docker pull ghcr.io/namehash/ensnode/ensrainbow:${{ needs.validate-and-prepare.outputs.docker-tag-base }}-${{ needs.validate-and-prepare.outputs.commit-sha }} + \`\`\` + + " + else + COMMENT_BODY+="**Docker Images:** ❌ Build failed - see [workflow logs](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + " + fi + fi + + # Publish target info + PUBLISH_TARGET_DISPLAY="" + if [[ "${{ inputs.publish_target }}" == "npm-only" ]]; then + PUBLISH_TARGET_DISPLAY="📦 NPM packages only" + elif [[ "${{ inputs.publish_target }}" == "npm-and-lambdas" ]]; then + PUBLISH_TARGET_DISPLAY="📦 NPM packages + ⚡ Lambda functions" + elif [[ "${{ inputs.publish_target }}" == "npm-and-ghcr" ]]; then + PUBLISH_TARGET_DISPLAY="📦 NPM packages + 🐳 Docker images" + elif [[ "${{ inputs.publish_target }}" == "all" ]]; then + PUBLISH_TARGET_DISPLAY="📦 NPM packages + ⚡ Lambda functions + 🐳 Docker images" + fi + + # Build info + COMMENT_BODY+="**Build Info:** + - 🎯 Target: \`${PUBLISH_TARGET_DISPLAY}\` + - 📦 Version: \`${{ needs.build-and-publish-npm.outputs.published-version }}\` + - 📝 Commit: \`${{ needs.validate-and-prepare.outputs.commit-sha }}\` + - 🌿 Branch: \`${{ github.ref_name }}\` + - ⏰ Built: \`${BUILD_TIME}\` + - 🔗 [Workflow Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}) + + --- + *🤖 This comment will be updated on subsequent publishing of release previews from the branch associated with this PR* + + > **Note:** Preview packages are managed by changesets. NPM dist-tags can be cleaned up manually using \`npm dist-tag rm @ensnode/ensnode-sdk ${{ needs.validate-and-prepare.outputs.dist-tag }}\`" + + # Save to file for proper multiline handling + echo "$COMMENT_BODY" > comment_body.txt + echo "comment-file=comment_body.txt" >> $GITHUB_OUTPUT + + - name: Find existing comment + id: find-comment + run: | + # Look for existing comment from github-actions bot + COMMENT_ID=$(gh pr view ${{ needs.validate-and-prepare.outputs.pr-number }} --json comments --jq '.comments[] | select(.author.login == "github-actions" and (.body | contains("🚀 Preview Packages"))) | .id' | head -1) + + if [[ -n "$COMMENT_ID" ]]; then + echo "existing-comment-id=${COMMENT_ID}" >> $GITHUB_OUTPUT + echo "✅ Found existing comment ID: ${COMMENT_ID}" + else + echo "ℹ️ No existing comment found, will create new one" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Update or create PR comment + run: | + if [[ -n "${{ steps.find-comment.outputs.existing-comment-id }}" ]]; then + # Update existing comment + gh pr comment ${{ needs.validate-and-prepare.outputs.pr-number }} --edit-last --body-file ${{ steps.comment.outputs.comment-file }} + echo "✅ Updated existing PR comment" + else + # Create new comment + gh pr comment ${{ needs.validate-and-prepare.outputs.pr-number }} --body-file ${{ steps.comment.outputs.comment-file }} + echo "✅ Created new PR comment" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Log summary + run: | + echo "## 📋 Preview Release Summary" + echo "- Branch: ${{ github.ref_name }}" + echo "- PR: #${{ needs.validate-and-prepare.outputs.pr-number }}" + echo "- Publish target: ${{ inputs.publish_target }}" + echo "- Published version: ${{ needs.build-and-publish-npm.outputs.published-version }}" + echo "- NPM dist-tag: ${{ needs.validate-and-prepare.outputs.dist-tag }}" + echo "- NPM publish: ${{ needs.build-and-publish-npm.result }}" + if [[ "${{ inputs.publish_target }}" == "npm-and-lambdas" || "${{ inputs.publish_target }}" == "all" ]]; then + echo "- Lambda publish: ${{ needs.build-and-publish-lambdas.result }}" + fi + if [[ "${{ inputs.publish_target }}" == "npm-and-ghcr" || "${{ inputs.publish_target }}" == "all" ]]; then + echo "- Docker tag: ${{ needs.validate-and-prepare.outputs.docker-tag-base }}-${{ needs.validate-and-prepare.outputs.commit-sha }}" + echo "- GHCR publish: ${{ needs.build-and-publish-ghcr.result }}" + fi diff --git a/.github/workflows/release_snapshot.yml b/.github/workflows/release_snapshot.yml new file mode 100644 index 000000000..aab1a1f06 --- /dev/null +++ b/.github/workflows/release_snapshot.yml @@ -0,0 +1,146 @@ +# Release Snapshot Workflow (@next) +# +# This workflow automatically creates snapshot releases for every commit to main. +# It allows users to install and test the latest features before they become full releases. +# +# How it works: +# 1. Triggered automatically on every push to main (except (full) Release PR merges) +# 2. Builds all packages +# 3. Versions packages using changesets based on the merged PR's changeset +# 4. Publishes NPM packages with @next tag +# 5. Builds and publishes Docker images with @next tag to GitHub Container Registry +# +# Published artifacts: +# - NPM packages: All @ensnode/* packages published with @next tag +# - Docker images: ensindexer, ensadmin, ensapi, ensrainbow published with @next tag +# - No GitHub releases or tags are created +# +# Version behavior: +# - Packages advance to the version indicated in the changeset of the merged PR +# - All packages use "fixed" versioning and advance together +# - Each snapshot represents the pre-stable state of main +# +# Use cases: +# - Development and testing environments +# - Testing upcoming features before stable release +# - Experimenting with new functionality +# - Integration testing with latest main branch +# +# Important notes: +# - Snapshot releases are NOT stable and should be used with caution +# - Only use @next for development/testing, not production +# - Automatically triggered - cannot be run manually +# - Only runs if there are changes to publish (hasChanges check) +# - Requires PR to be approved by ensnode team (main branch is protected) +# - Installation: npm install @ensnode/package-name@next +# +# Documentation: https://ensnode.io/docs/contributing/releases#snapshot-release + +name: Release Snapshot (@next) + +on: + push: + branches: + - main + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + build-and-publish-npm: + name: Build & Publish Snapshot Packages to NPM + if: github.repository == 'namehash/ensnode' && !contains(github.event.head_commit.message, 'changeset-release/main') + runs-on: blacksmith-4vcpu-ubuntu-2204 + permissions: + contents: read + id-token: write + outputs: + hasChanges: ${{ steps.snapshot.outputs.hasChanges }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: ./.github/actions/setup_node_environment + + - name: Build snapshot packages + run: pnpm packages:prepublish + + - name: Version snapshot packages + id: snapshot + run: | + pnpm changeset:next + # Check if there are any changes to publish + if [ -n "$(git status --porcelain)" ]; then + echo "hasChanges=true" >> $GITHUB_OUTPUT + else + echo "hasChanges=false" >> $GITHUB_OUTPUT + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish snapshot packages to NPM + if: steps.snapshot.outputs.hasChanges == 'true' + run: pnpm changeset-publish:next + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + + build-and-publish-ghcr: + name: Build & Publish Snapshot Docker Images to GHCR + runs-on: blacksmith-4vcpu-ubuntu-2204 + if: github.repository == 'namehash/ensnode' && !contains(github.event.head_commit.message, 'changeset-release/main') && needs.snapshot.outputs.hasChanges == 'true' + needs: [build-and-publish-npm] + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + app: [ensindexer, ensadmin, ensrainbow, ensapi] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Generate docker image snapshot tag + id: snapshot-tag + run: | + TIMESTAMP=$(date +%Y%m%d-%H%M%S) + SHORT_SHA=$(git rev-parse --short HEAD) + SNAPSHOT_TAG="next-${TIMESTAMP}-${SHORT_SHA}" + echo "tag=$SNAPSHOT_TAG" >> $GITHUB_OUTPUT + + - name: Build and publish snapshot docker images to GHCR + uses: ./.github/actions/build_docker_image + with: + image: ghcr.io/${{ github.repository }}/${{ matrix.app }} + dockerfile: apps/${{ matrix.app }}/Dockerfile + registry_user: ${{ github.actor }} + registry_token: ${{ secrets.GITHUB_TOKEN }} + tags: | + type=raw,value=next + type=raw,value=${{ steps.snapshot-tag.outputs.tag }} + type=sha + + build-and-publish-lambdas: + name: Build & Publish Snapshot Lambdas + runs-on: blacksmith-4vcpu-ubuntu-2204 + if: github.repository == 'namehash/ensnode' && !contains(github.event.head_commit.message, 'changeset-release/main') && needs.snapshot.outputs.hasChanges == 'true' + needs: [build-and-publish-npm] + strategy: + fail-fast: false + matrix: + lambda: [fallback-ensapi] + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Build and Publish Lambda + uses: ./.github/actions/build_and_publish_lambda + with: + name: ${{ matrix.lambda }} + version: next diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index cb0b82812..1e4419d58 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -13,10 +13,7 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2204 steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: useblacksmith/setup-node@v5 - with: - node-version-file: .nvmrc + - uses: ./.github/actions/setup_node_environment - run: pnpm audit --audit-level=moderate prepublish: @@ -24,11 +21,7 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2204 steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: useblacksmith/setup-node@v5 - with: - node-version-file: .nvmrc - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup_node_environment - run: pnpm packages:prepublish static-analysis: @@ -36,11 +29,7 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2204 steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: useblacksmith/setup-node@v5 - with: - node-version-file: .nvmrc - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup_node_environment - name: Run Biome CI run: pnpm lint:ci @@ -56,11 +45,7 @@ jobs: runs-on: blacksmith-4vcpu-ubuntu-2204 steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: useblacksmith/setup-node@v5 - with: - node-version-file: .nvmrc - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup_node_environment - run: pnpm test integrity-check: @@ -83,11 +68,7 @@ jobs: --health-retries 5 steps: - uses: actions/checkout@v4 - - uses: pnpm/action-setup@v4 - - uses: useblacksmith/setup-node@v5 - with: - node-version-file: .nvmrc - - run: pnpm install --frozen-lockfile + - uses: ./.github/actions/setup_node_environment # This will run the dev command in background, and wait up to # HEALTH_CHECK_TIMEOUT seconds. It will monitor the log output to diff --git a/docs/ensnode.io/src/content/docs/docs/contributing/releases.mdx b/docs/ensnode.io/src/content/docs/docs/contributing/releases.mdx index ce5a6e62e..def6d65e7 100644 --- a/docs/ensnode.io/src/content/docs/docs/contributing/releases.mdx +++ b/docs/ensnode.io/src/content/docs/docs/contributing/releases.mdx @@ -7,41 +7,161 @@ sidebar: import { LinkCard } from '@astrojs/starlight/components'; -We use [changesets](https://github.com/changesets/changesets) to manage version bumps and release notes for our NPM packages and Docker images (ENSIndexer, ENSAdmin, and ENSRainbow). +We use [changesets](https://github.com/changesets/changesets) to manage version bumps and release notes for our monorepo's artifacts (NPM packages, Docker images, and Lambda zips). ## Adding a Changeset to Your PR When you open a PR with feature or bug fix changes, you'll need to include a changeset file that documents your changes: -1. Run `changeset` or `pnpm changeset` in the repository root -2. Select the packages/apps your changes affect using the interactive prompt +1. Run `changeset` or `pnpm changeset` in the repository root. +2. Select the packages/apps your changes affect using the interactive prompt. 3. Choose whether your changes are "major" (breaking changes), "minor" (features), or "patch" (bug fixes). -4. Write a clear description of your changes - this will appear in the release notes +4. Write a clear description of your changes - this will appear in the release notes. The `changesets/bot` will automatically comment on your PR to either remind you to add a changeset or confirm the version changes that will happen when your PR is merged. **Important notes:** -- Changesets are only required for user-facing changes (features, bug fixes) -- You don't need a changeset for documentation changes or internal refactoring -- All our packages use "fixed" versioning - they all share the same version number regardless of which package triggered the version bump +- Changesets are only required for user-facing changes (features, bug fixes). +- You don't need a changeset for documentation changes or internal refactoring. +- All our apps and packages (with an exception as noted below) use "fixed" versioning - they all share the same version number regardless of which app or package triggered the version bump. +- An exception to the above "fixed" versioning is the "fallback-ensapi" app. This is a Lambda containing logic specific to NameHash deployments of ENSNode and is versioned independently. -## Upon a New Release +# Creating a Release -Upon the publishing of a new release, your change will be included in the produced packages/images and your contributions will be referenced in the GitHub Release notes. +Upon the publishing of a new release, your change will be included in the produced artifacts and your contributions will be referenced in the GitHub Release notes. There are three different types of releases that your changes can be included in for produced artifacts. -## Publishing Release Candidates (RC) of packages to NPM +## Full Release -Publishing release candidates (RCs) of packages to NPM supports apps outside of the ENSNode monorepo to make use of prerelease packages for testing new ENSNode versions before making a full ENSNode release. Package RCs are published to NPM with the `rc` dist-tag: +Workflow File: [release.yml](https://github.com/namehash/ensnode/blob/main/.github/workflows/release.yml) -1. Ensure your changesets are committed to `main` -2. Navigate to [Actions > Release NPM RC](https://github.com/namehash/ensnode/actions/workflows/npm-release-rc.yml) -3. Click "Run workflow" and select the `main` branch -4. RC packages are published to NPM with the `rc` tag (e.g., `1.0.0-rc.0`) -5. Install RCs with: `npm install @ensnode/package-name@rc` +If your PR includes a changeset and is merged to `main` then it will automatically be added to a new automated Release PR by the Changesets bot. As more changesets are added to `main` the Release PR will continue to update. Once a Release PR is merged into `main` it triggers a "full" release that will: + +- Build and publish all of the monorepo's artifacts (NPM packages, Docker images, and Lambda zips). +- Create a release on GitHub with autogenerated release notes from all the included changesets. + +**Important notes:** +- Among all release types, only Full Releases are considered stable. +- Full releases are triggered through merging the Release PR to `main`. +- All ENSNode packages use "fixed" versioning. Once a full release is published they will all advance to the version indicated in the Release PR based on the included changesets. +- Only members of the NameHash Labs `ensnode` team have the required permissions to merge the Release PR to `main`. +- Full releases will create GitHub tags and release notes. + +## Snapshot Release + +Workflow File: [release_snapshot.yml](https://github.com/namehash/ensnode/blob/main/.github/workflows/release_snapshot.yml) + +Each commit to `main` will automatically trigger the `release_snapshot.yml` workflow to build and publish all of the monorepo's artifacts. These public releases will be under the tag `@next`, allowing anyone to use the artifacts associated with each commit to main. To install snapshot releases run `pnpm install @ensnode/[package-name]@next` or `docker run ghcr.io/namehash/ensnode/[app-name]:next`. + +**Important notes:** +- Snapshot releases are part of the pre-stable state of the `main` branch and should be installed with caution until a [full release](#full-release) is published. +- Release snapshots are automatic and cannot be triggered manually. +- Snapshot releases will include the `@next` tag for published artifacts. +- Published artifacts will advance to the version that was included in the changeset of the PR merged to `main`. +- The `main` branch of `ensnode` is protected. Only PRs approved by the `ensnode` team can be merged to `main` and trigger a snapshot release. +- No GitHub releases or tags are created for snapshot releases. + +## Preview Release + +Workflow File: [release_preview.yml](https://github.com/namehash/ensnode/blob/main/.github/workflows/release_preview.yml) + +To test or install a package before merging it into `main`, a preview release can be used. Each preview release is associated with a PR, and the PR will receive a comment with installation instructions. To manually trigger a preview release, follow these steps: + +1. Navigate to [Actions > Release Preview](https://github.com/namehash/ensnode/actions/workflows/release_preview.yml) +2. Click "Run workflow" and select from the following options: + - The branch on which to run the preview release workflow. The branch must have an open PR. + - Choose which artifacts to build and publish: + - `npm-only`: NPM packages only + - `npm-and-lambdas`: NPM packages + Lambda functions + - `npm-and-ghcr`: NPM packages + Docker images + - `all`: NPM packages + Lambda functions + Docker images + - (Optional) Provide a custom suffix for the preview release tag. For example, if you had a branch called `feat/add-api-route` and left this custom suffix field blank, the preview release would be `@ensnode/[package-name]@preview-feat-add-api-route`. If you fill in the custom suffix field with `users-route` then the resulting tag name would be `@ensnode/[package-name]@preview-users-route`. +3. The workflow will post a comment on the PR with installation instructions. If multiple preview releases are triggered for the same PR, the comment will update with the latest release info. +4. Install preview packages with: `npm install @ensnode/[package-name]@preview-branch-name`. **Important notes:** -- NPM Package RC versions are ephemeral - they don't create commits or modify the repository -- Each workflow run increments the RC version (rc.0, rc.1, rc.2, etc.) -- Docker images are NOT built for RCs -- No GitHub releases or tags are created for RCs -- The `main` branch remains unchanged with regular versions +- Preview releases require an open PR. The workflow will abort if no PR exists for the branch. +- Preview releases are not guaranteed to be stable as they are still under active development. +- Preview releases can only be triggered manually by authorized NameHash team members. +- Preview releases will include the `@preview-*` tag for published artifacts, followed by either the name of the branch or the custom suffix chosen during the action trigger. +- Published artifacts will advance to the version that was included in the changeset of the selected branch. +- No GitHub releases or tags are created for preview releases. + +# Selecting a Release for Deployment or Installation + +:::caution +ENSNode is currently not using [Semantic Versioning](https://semver.org/). Patches and minor releases may include breaking changes. +::: + +When using ENSNode artifacts, you have several release types to choose from. + +## Where to Find Releases + +- **NPM Packages**: Available on the [npm registry](https://www.npmjs.com/search?q=%40ensnode) under the `@ensnode` organization. +- **Docker Images**: Available on [GitHub Container Registry](https://github.com/orgs/namehash/packages?repo_name=ensnode). +- **GitHub Releases**: Full releases are documented with release notes on the [ENSNode GitHub releases page](https://github.com/namehash/ensnode/releases). +- **Lambda Zip Artifacts**: Available in the [Artifact section](https://github.com/actions/upload-artifact?tab=readme-ov-file#where-does-the-upload-go) of a successful workflow run. These Action Artifacts are [retained for 90 days](https://github.com/actions/upload-artifact?tab=readme-ov-file#retention-period). + +## Choosing the Right Release Type + +### Pinned Full Release Versions + +When deploying ENSNode to production environments, it is advisable to use a Pinned Full Release. Pinned full releases are required for those who want to use any published ENSNode artifacts. By using a pinned version you can maintain full control over features or patches that might be included. Review the release notes on the [Releases Page](https://github.com/namehash/ensnode/releases) to help decide which version to install. + +```bash +npm install @ensnode/[package-name]@[version] +docker run ghcr.io/namehash/ensnode/[app-name]:[version] +``` + +:::caution +When installing NPM packages for use in production environments, it is also advisable to pin a specific Pinned Full Release version number. ENSNode patch version increments MAY include breaking changes, so the usage of exact version package specifiers is encouraged. + +For example: + +✅ `"@ensnode/[package-name]": "1.0.0"` +❌ `"@ensnode/[package-name]": "^1.0.0"` + +And when installing from the command line: + +✅ `pnpm install @ensnode/[package-name]@1.0.0` +❌ `pnpm install @ensnode/[package-name]` +::: + +:::caution +In particular, when deploying ENSNode to production environments, using the `latest` Docker tag without strict pull policies could result in a version mismatch between interdependent ENSNode apps (which will helpfully crash at startup). Due to this, we highly recommend using a specific Pinned Full Release version number tag like `1.0.0` instead of the tag `latest`, which could point to different versions of the ENSNode app depending on the platform's pull policy. +::: + +:::caution +Each ENSIndexer version update is likely to produce an updated [Ponder Build Id](https://ponder.sh/docs/api-reference/ponder/database). When updating your ENSIndexer version, you should expect to update the `DATABASE_SCHEMA` environment variable to point to a new Postgres Database Schema for a complete reindexing with the new Ponder Build Id. A complete reindexing may take over 24 hours depending on your configuration. ENSNode version updates require special coordination and should not be assumed to be a simple version bump. +::: + + +### Snapshot Releases +:::caution +The `next` tag is a floating pointer that always references the most recent snapshot release. When using Docker images with the `next` tag, you must run `docker pull` to update your local Docker cache if you want to get the actual latest image. Without pulling, you may continue using an older cached version. +::: + +```bash +npm install @ensnode/[package-name]@next +docker run ghcr.io/namehash/ensnode/[app-name]:next +``` + +Snapshot releases should be used by those who need to test features or patches before they are included in full releases. These releases follow the `main` branch and are not referenced in the GitHub Releases page. Instead they are installed by using the `next` tag for published artifacts. + +:::caution +Snapshot releases may contain unstable changes and should only be used in development environments. +::: + +### Preview Releases +:::note +The example below is a mock of what a preview release might look like. Read the [preview releases section](#preview-release) for more information. +::: +```bash +npm install @ensnode/[package-name]@[preview-branch-name] +docker run ghcr.io/namehash/ensnode/[app-name]:[preview-branch-name] +``` + +Preview releases are designed to test features or patches on a PR branch before it is merged to `main`. Each preview release is associated with a PR, and the PR will have a comment with installation instructions. Since preview releases can contain experimental and unstable changes, they should be avoided unless you are actively contributing through a PR and need to test work on a branch. + +:::caution +Avoid using preview releases unless you are actively contributing to ENSNode and need to test changes on an active PR. +::: diff --git a/package.json b/package.json index 1c82ae5e6..999baac65 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "test": "vitest --silent passed-only", "typecheck": "pnpm -r --parallel --aggregate-output typecheck", "changeset": "changeset", + "changeset:next": "changeset version --snapshot next", "changeset-publish": "changeset publish", + "changeset-publish:next": "changeset publish --no-git-tag --snapshot --tag next", "packages:prepublish": "pnpm -r prepublish", "docker:build:ensnode": "pnpm run -w --parallel \"/^docker:build:.*/\"", "docker:build:ensindexer": "docker build -f apps/ensindexer/Dockerfile -t ghcr.io/namehash/ensnode/ensindexer:latest .",