Repository: gleam-lang/gleam
Pull request #4275: Simplify CI Release Process
Author: login: maennchen
Created: 2025-02-21T22:28:48Z | Merged: 2025-03-20T12:47:56Z

* Preparation for #4276
* Unify wasm / native release generation
* Unify nightly / version release generation
* The result should be identical to the current CI
* Example CI / Releases
  * Tagged CI (push) https://github.com/maennchen/gleam/actions/runs/13813751330
  * Tagged CI (release) https://github.com/maennchen/gleam/actions/runs/13813919061
  * Tagged Release https://github.com/maennchen/gleam/releases/tag/vSimplify
  * Nightly CI: https://github.com/maennchen/gleam/actions/runs/13813756622
  * Nightly Release: https://github.com/maennchen/gleam/releases/tag/nightly


Diff:
diff --git a/.github/actions/build-container/action.yml b/.github/actions/build-container/action.yml
new file mode 100644
index 00000000000..ac4620021d6
--- /dev/null
+++ b/.github/actions/build-container/action.yml
@@ -0,0 +1,83 @@
+name: "Build Gleam"
+description: "Build Gleam Container"
+inputs:
+  version:
+    description: "Build Version"
+    required: true
+  release-id:
+    description: "Release ID"
+    required: true
+
+runs:
+  using: composite
+  steps:
+  - name: Download Gleam release assets
+    uses: robinraju/release-downloader@v1
+    with:
+      releaseId: "${{ inputs.release-id }}"
+      fileName: "gleam-${{ inputs.version }}-{x86_64-unknown-linux-musl,aarch64-unknown-linux-musl}.tar.gz"
+
+  - name: "Unpack release files into correct location"
+    shell: bash
+    run: |
+      declare -A ARCH
+      ARCH["amd64"]="x86_64-unknown-linux-musl"
+      ARCH["arm64"]="aarch64-unknown-linux-musl"
+
+      for SHORT in "${!ARCH[@]}"; do
+        LONG="${ARCH[$SHORT]}"
+
+        # Unpack Release
+        tar xf "gleam-$VERSION-$LONG.tar.gz"
+
+        # Move files into place
+        mv gleam "gleam-$SHORT"
+
+        # Delete Unused Files
+        rm -rf "gleam-$VERSION-$LONG*"
+      done
+    env:
+      VERSION: "${{ inputs.version }}"
+
+  - name: Authenticate with GitHub container registry
+    uses: docker/login-action@v3
+    with:
+      registry: ghcr.io
+      username: ${{ github.actor }}
+      password: ${{ github.token }}
+
+  - name: Set up Docker Buildx
+    uses: docker/setup-buildx-action@v3
+
+  - name: Build version and tags
+    shell: bash
+    id: versions
+    run: |
+      # Strip `v` prefix from version
+      BARE_VERSION=$(echo "$VERSION" | sed -e 's|^v/\(.*\)|\1|')
+
+      # Build version with platform
+      PLATFORM_VERSION=$BARE_VERSION-${{ matrix.base-image }}
+
+      # Build container tag
+      TAG=ghcr.io/${{ github.repository }}:$PLATFORM_VERSION
+
+      echo "platform-version=$PLATFORM_VERSION" >> $GITHUB_OUTPUT
+      echo "container-tag=$TAG" >> $GITHUB_OUTPUT
+    env:
+      VERSION: "${{ inputs.version }}"
+
+  - name: Build and push
+    uses: docker/build-push-action@v6
+    with:
+      context: .
+      platforms: linux/amd64,linux/arm64
+      file: containers/${{ matrix.base-image }}.dockerfile
+      push: true
+      tags: ${{ steps.versions.outputs.container-tag }}
+      labels: |
+        org.opencontainers.image.title=gleam
+        org.opencontainers.image.url=https://gleam.run
+        org.opencontainers.image.source=https://github.com/gleam-lang/gleam
+        org.opencontainers.image.version=${{ steps.versions.outputs.platform-version }}
+        org.opencontainers.image.licenses=Apache-2.0
diff --git a/.github/actions/build-release/action.yml b/.github/actions/build-release/action.yml
new file mode 100644
index 00000000000..37a37bda0bc
--- /dev/null
+++ b/.github/actions/build-release/action.yml
@@ -0,0 +1,143 @@
+name: "Build Gleam"
+description: "Build Gleam Release"
+inputs:
+  version:
+    description: "Build Version"
+    required: true
+  toolchain:
+    description: "Cargo Toolchain"
+    required: true
+  target:
+    description: "Cargo Installation Target"
+    required: true
+  cargo-tool:
+    description: "Cargo Tool used for Build (for example, `cross`)"
+    required: true
+  expected-binary-architecture:
+    description: "Expected Binary Architecture"
+    required: false
+    default: ""
+
+outputs:
+  archive:
+    description: "Path to build asset"
+    value: "${{ steps.build.outputs.archive }}"
+  files:
+    description: "Path to all files"
+    value: |
+      ${{ steps.build.outputs.archive }}
+      ${{ steps.build.outputs.archive }}.sha256
+      ${{ steps.build.outputs.archive }}.sha512
+
+runs:
+  using: "composite"
+  steps:
+    - name: Install Rust toolchain
+      uses: actions-rust-lang/setup-rust-toolchain@v1
+      with:
+        toolchain: ${{ inputs.toolchain }}
+        target: ${{ inputs.target }}
+        cache-key: v1-${{ inputs.target }}
+
+    - name: Build WASM release binary
+      if: ${{ inputs.target != 'wasm32-unknown-unknown' }}
+      uses: clechasseur/rs-cargo@v3
+      with:
+        command: build
+        args: --release --target ${{ inputs.target }}
+        tool: ${{ inputs.cargo-tool }}
+
+    - name: Install wasm-pack
+      if: ${{ inputs.target == 'wasm32-unknown-unknown' }}
+      shell: bash
+      run: curl -sSL https://rustwasm.github.io/wasm-pack/installer/init.sh | sh
+
+    - name: Build WASM release binary
+      if: ${{ inputs.target == 'wasm32-unknown-unknown' }}
+      shell: bash
+      run: wasm-pack build --release --target web compiler-wasm
+
+    - name: Verify binary architecture
+      if: ${{ inputs.expected-binary-architecture }}
+      shell: bash
+      run: |
+        BINARY_PATH="target/${{ inputs.target }}/release/gleam"
+        if [[ "${{ inputs.target }}" == *"windows"* ]]; then
+          BINARY_PATH="${BINARY_PATH}.exe"
+        fi
+
+        if ! file -b "$BINARY_PATH" | grep -q "${{ inputs.expected-binary-architecture }}"; then
+          echo "error: Architecture mismatch"
+          echo "Expected architecture: '${{ inputs.expected-binary-architecture }}'"
+          echo "Found binary type: '$(file -b "$BINARY_PATH")'"
+          exit 1
+        fi
+        echo "ok: Architecture match"
+
+    - name: Build archive
+      id: build
+      shell: bash
+      run: |
+        case "$TARGET" in
+          *windows*)
+            ARCHIVE="gleam-$VERSION-$TARGET.zip"
+            cp "target/$TARGET/release/gleam.exe" "gleam.exe"
+            7z a "$ARCHIVE" "gleam.exe"
+            rm gleam.exe
+            ;;
+          wasm*)
+            ARCHIVE="gleam-$VERSION-browser.tar.gz"
+            tar -C compiler-wasm/pkg/ -czvf $ARCHIVE .
+            rm -rf compiler-wasm/pkg/
+            ;;
+          *)
+            ARCHIVE="gleam-$VERSION-$TARGET.tar.gz"
+            cp "target/$TARGET/release/gleam" "gleam"
+            tar -czvf "$ARCHIVE" "gleam"
+            rm gleam
+            ;;
+        esac
+
+        echo "archive=$ARCHIVE" >> $GITHUB_OUTPUT
+      env:
+        TARGET: "${{ inputs.target }}"
+        VERSION: "${{ inputs.version }}"
+
+    - name: Ensure binary successfully boots
+      if: ${{ inputs.expected-binary-architecture }}
+      shell: bash
+      run: |
+        case "$TARGET" in
+          x86_64-pc-windows-msvc)
+            7z x "$ARCHIVE"
+            ./gleam.exe --version
+            ;;
+          aarch64*)
+            echo "We cannot test an ARM binary on a AMD64 runner"
+            ;;
+          *)
+            tar -xvzf "$ARCHIVE"
+            ./gleam --version
+            ;;
+        esac
+      env:
+        TARGET: "${{ inputs.target }}"
+        ARCHIVE: "${{ steps.build.outputs.archive }}"
+
+    - name: Hash Build Archive
+      shell: bash
+      run: |
+        openssl dgst -r -sha256 -out "$ARCHIVE".sha256 "$ARCHIVE"
+        openssl dgst -r -sha512 -out "$ARCHIVE".sha512 "$ARCHIVE"
+      env:
+        ARCHIVE: "${{ steps.build.outputs.archive }}"
+
+    - name: Upload artifact
+      uses: actions/upload-artifact@v4
+      with:
+        name: release-${{ matrix.target }}
+        path: |
+          ${{ steps.build.outputs.archive }}
+          ${{ steps.build.outputs.archive }}.sha256
+          ${{ steps.build.outputs.archive }}.sha512
+        overwrite: true
diff --git a/.github/workflows/release-containers.yaml b/.github/workflows/release-containers.yaml
index efe76f0e708..6ce798c3810 100644
--- a/.github/workflows/release-containers.yaml
+++ b/.github/workflows/release-containers.yaml
@@ -4,10 +4,15 @@ on:
     types:
       - "published"
 
+permissions:
+  packages: write
+
 jobs:
   publish-container-images:
     name: publish-container-images
     runs-on: ubuntu-latest
+    # Covered by `release-nightly.yaml`
+    if: ${{ github.event.release.tag_name != 'nightly' }}
     strategy:
       matrix:
         base-image:
@@ -25,69 +30,13 @@ jobs:
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4
-
-      - name: Authenticate with GitHub container registry
-        uses: docker/login-action@v3
         with:
-          registry: ghcr.io
-          username: lpil
-          password: ${{ secrets.CONTAINER_REGISTRY_PERSONAL_ACCESS_TOKEN }}
-
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
-
-      - name: Build version and tags
-        id: versions
-        run: |
-          # Strip git ref prefix from version
-          V_VERSION=$(echo "${{ github.ref }}" | sed -e 's|.*/\(.*\)|\1|')
-
-          # Strip `v` prefix from version
-          BARE_VERSION=$(echo "$V_VERSION" | sed -e 's|^v/\(.*\)|\1|')
-
-          # Build version with platform
-          PLATFORM_VERSION=$BARE_VERSION-${{ matrix.base-image }}
-
-          # Build container tag
-          TAG=ghcr.io/${{ github.repository }}:$PLATFORM_VERSION
-
-          echo "v_version=$V_VERSION" >> $GITHUB_OUTPUT
-          echo "bare_version=$BARE_VERSION" >> $GITHUB_OUTPUT
-          echo "platform_version=$PLATFORM_VERSION" >> $GITHUB_OUTPUT
-          echo "container_tag=$TAG" >> $GITHUB_OUTPUT
-
-      - name: Download Gleam archives from GitHub release
-        run: |
-          VERSION=${{ steps.versions.outputs.v_version }}
-
-          AMD_URL=https://github.com/${{ github.repository }}/releases/download/$VERSION/gleam-$VERSION-x86_64-unknown-linux-musl.tar.gz
-          ARM_URL=https://github.com/${{ github.repository }}/releases/download/$VERSION/gleam-$VERSION-aarch64-unknown-linux-musl.tar.gz
-
-          echo Downloading amd $AMD_URL
-          curl -Lo gleam-amd.tar.gz $AMD_URL
-
-          echo Downloading arm $ARM_URL
-          curl -Lo gleam-arm.tar.gz $ARM_URL
-
-      - name: Unpack Gleam binary from archive
-        run: |
-          tar xf gleam-amd.tar.gz
-          mv gleam gleam-amd64
-
-          tar xf gleam-arm.tar.gz
-          mv gleam gleam-arm64
+          sparse-checkout: |
+            .github/actions
+            containers
 
-      - name: Build and push
-        uses: docker/build-push-action@v6
+      - name: "Build & Push Container"
+        uses: "./.github/actions/build-container"
         with:
-          context: .
-          platforms: linux/amd64,linux/arm64
-          file: containers/${{ matrix.base-image }}.dockerfile
-          push: true
-          tags: ${{ steps.versions.outputs.container_tag }}
-          labels: |
-            org.opencontainers.image.title=gleam
-            org.opencontainers.image.url=https://gleam.run
-            org.opencontainers.image.source=https://github.com/gleam-lang/gleam
-            org.opencontainers.image.version=${{ steps.versions.outputs.platform_version }}
-            org.opencontainers.image.licenses=Apache-2.0
+          release-id: ${{ github.event.release.id }}
+          version: ${{ github.ref_name }}
diff --git a/.github/workflows/release-nightly.yaml b/.github/workflows/release-nightly.yaml
index 7a2021dc012..7de205dce45 100644
--- a/.github/workflows/release-nightly.yaml
+++ b/.github/workflows/release-nightly.yaml
@@ -9,31 +9,35 @@ env:
   CARGO_TERM_COLOR: always
   RUSTFLAGS: "-D warnings"
 
+permissions:
+  contents: write
+  packages: write
+
 jobs:
-  nightly-last-run:
+  # Check if the actions already ran in the last 24 hours
+  # * If yes: Don't run again
+  # * If no: Clean out existing release assets
+  prepare-nightly:
     runs-on: ubuntu-latest
-    name: Check latest commit
+    name: Prepare Nightly Release
     outputs:
-      should_run: ${{ steps.should_run.outputs.should_run }}
-    if: ${{ github.repository_owner == 'gleam-lang' }}
+      should-run: ${{ steps.should-run.outputs.should-run }}
+    # TODO: Re-add
+    # if: ${{ github.repository == 'gleam-lang/gleam' }}
     steps:
       - uses: actions/checkout@v4
       - name: print latest_commit
         run: echo ${{ github.sha }}
 
-      - id: should_run
+      - id: should-run
         continue-on-error: true
         name: check latest commit is less than a day
         if: ${{ github.event_name == 'schedule' }}
-        run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should_run=false" >> $GITHUB_OUTPUT
+        run: test -z $(git rev-list --after="24 hours" ${{ github.sha }}) && echo "should-run=false" >> $GITHUB_OUTPUT
 
-  build-nightly-clean:
-    runs-on: ubuntu-latest
-    needs: [nightly-last-run]
-    if: ${{ github.repository_owner == 'gleam-lang' && needs.nightly-last-run.outputs.should_run != 'false' }}
-    steps:
       - name: Delete old release assets
         uses: mknejp/delete-release-assets@v1
+        if: ${{ steps.should-run != 'false' }}
         with:
           token: ${{ github.token }}
           tag: nightly
@@ -45,11 +49,11 @@ jobs:
             *.sha256
             *.sha512
 
-  build-nightly:
+  build-release:
     name: build-release
     runs-on: ${{ matrix.os }}
-    needs: [build-nightly-clean]
-    if: ${{ github.repository_owner == 'gleam-lang' }}
+    outputs:
+      release-id: ${{ steps.release.outputs.id }}
     strategy:
       matrix:
         target:
@@ -58,145 +62,76 @@ jobs:
           - x86_64-apple-darwin
           - aarch64-apple-darwin
           - x86_64-pc-windows-msvc
-        toolchain: [stable]
+        toolchain: [ stable ]
         include:
           - os: ubuntu-latest
             target: x86_64-unknown-linux-musl
-            binary: x86-64
+            expected-binary-architecture: x86-64
             cargo-tool: cross
           - os: ubuntu-latest
             target: aarch64-unknown-linux-musl
-            binary: aarch64
+            expected-binary-architecture: aarch64
             cargo-tool: cross
           # macos>=14 runs exclusively on aarch64 and will thus fail to execute properly for x64
           - os: macos-13
             target: x86_64-apple-darwin
-            binary: x86_64
+            expected-binary-architecture: x86_64
             cargo-tool: cargo
           - os: macos-latest
             target: aarch64-apple-darwin
-            binary: arm64
+            expected-binary-architecture: arm64
             cargo-tool: cargo
           - os: windows-latest
             target: x86_64-pc-windows-msvc
-            binary: x86-64
+            expected-binary-architecture: x86-64
             cargo-tool: cargo
+          - os: ubuntu-latest
+            target: wasm32-unknown-unknown
+            cargo-tool: wasm-pack
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4
 
       - name: Update versions
         shell: bash
-        run: |
-          ./bin/add-nightly-suffix-to-versions.sh
+        run: ./bin/add-nightly-suffix-to-versions.sh
 
-      - name: Install Rust toolchain
-        uses: dtolnay/rust-toolchain@stable
+      - name: "Build Archive"
+        id: build
+        uses: "./.github/actions/build-release"
         with:
+          version: nightly
           toolchain: ${{ matrix.toolchain }}
           target: ${{ matrix.target }}
-
-      - name: Handle Rust dependencies caching
-        uses: Swatinem/rust-cache@v2
-        with:
-          key: v1-${{ matrix.target }}
-
-      - name: Build release binary
-        uses: clechasseur/rs-cargo@v3
-        with:
-          command: build
-          args: --release --target ${{ matrix.target }}
-          tool: ${{ matrix.cargo-tool }}
-
-      - name: Verify binary architecture
-        shell: bash
-        run: |
-          BINARY_PATH="target/${{ matrix.target }}/release/gleam"
-          if [[ "${{ matrix.target }}" == *"windows"* ]]; then
-            BINARY_PATH="${BINARY_PATH}.exe"
-          fi
-
-          if ! file -b "$BINARY_PATH" | grep -q "${{ matrix.binary }}"; then
-            echo "error: Architecture mismatch"
-            echo "Expected architecture: '${{ matrix.binary }}'"
-            echo "Found binary type: '$(file -b "$BINARY_PATH")'"
-            exit 1
-          fi
-          echo "ok: Architecture match"
-
-      - name: Build archive
-        shell: bash
-        run: |
-          VERSION=nightly
-          DAY=$(date +"%y%m%d")
-
-          if [ "${{ matrix.os }}" = "windows-latest" ]; then
-            ARCHIVE="gleam-$VERSION-${{ matrix.target }}.zip"
-            cp "target/${{ matrix.target }}/release/gleam.exe" "gleam.exe"
-            7z a "$ARCHIVE" "gleam.exe"
-            rm gleam.exe
-          else
-            ARCHIVE="gleam-$VERSION-${{ matrix.target }}.tar.gz"
-            cp "target/${{ matrix.target }}/release/gleam" "gleam"
-            tar -czvf "$ARCHIVE" "gleam"
-            rm gleam
-          fi
-
-          openssl dgst -r -sha256 -out "$ARCHIVE".sha256 "$ARCHIVE"
-          openssl dgst -r -sha512 -out "$ARCHIVE".sha512 "$ARCHIVE"
-          echo "ASSET=$ARCHIVE" >> $GITHUB_ENV
-          echo "DAY=$DAY" >> $GITHUB_ENV
-
-      - name: Ensure binary successfully boots
-        shell: bash
-        run: |
-          case "${{ matrix.target }}" in
-            x86_64-pc-windows-msvc)
-              7z x "$ASSET"
-              ./gleam.exe --version ;;
-            aarch64*)
-              echo "We cannot test an ARM binary on a AMD64 runner" ;;
-            *)
-              tar -xvzf "$ASSET"
-              ./gleam --version ;;
-          esac
+          cargo-tool: ${{ matrix.cargo-tool }}
+          expected-binary-architecture: ${{ matrix.expected-binary-architecture }}
 
       - name: Upload release archive
-        shell: bash
-        run: |
-          for i in $(seq 5); do
-            gh release upload nightly --clobber \
-              "${{ env.ASSET }}" \
-              "${{ env.ASSET }}.sha256" \
-              "${{ env.ASSET }}.sha512" \
-            && break
-          done
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-
-      - name: Upload x86_64-unknown-linux-musl artifact
-        uses: actions/upload-artifact@v4
-        with:
-          name: gleam-amd64
-          path: target/${{ matrix.target }}/release/gleam
-          overwrite: true
-        if: ${{ matrix.target == 'x86_64-unknown-linux-musl' }}
-
-      - name: Upload aarch64-unknown-linux-musl artifact
-        uses: actions/upload-artifact@v4
+        id: release
+        # https://github.com/softprops/action-gh-release/issues/445
+        # uses: softprops/action-gh-release@v2
+        uses: softprops/action-gh-release@0bd7e8b279c9b5b36661d552472fbbfe671fe26e
         with:
-          name: gleam-arm64
-          path: target/${{ matrix.target }}/release/gleam
-          overwrite: true
-        if: ${{ matrix.target == 'aarch64-unknown-linux-musl' }}
-
-  build-nightly-container-images:
+          draft: false
+          prerelease: true
+          fail_on_unmatched_files: true
+          files: "${{ steps.build.outputs.files }}"
+          tag_name: nightly
+          name: "Nightly"
+          body: |
+            A build that runs every night following changes to the main branch. Contains all the latest features and changes, but there is a higher chance of instability, bugs, and unfinished features.
+
+            The assets are updated but the tag is not, so if you view the commit or download the source from below you will not get the correct code.
+
+  build-container-images:
+    name: build-container-images
+    needs: [ prepare-nightly, build-release ]
     runs-on: ubuntu-latest
-    needs: [build-nightly]
-    if: ${{ github.repository_owner == 'gleam-lang' && needs.nightly-last-run.outputs.should_run != 'false' }}
+    if: ${{ needs.prepare-nightly.outputs.should-run != 'false' }}
     strategy:
       matrix:
         base-image:
+          - scratch
           - erlang
           - erlang-slim
           - erlang-alpine
@@ -210,41 +145,13 @@ jobs:
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4
-
-      - name: Download Gleam amd binary from previous job
-        uses: actions/download-artifact@v4
-        with:
-          name: gleam-amd64
-          path: ./gleam-amd64
-
-      - name: Download Gleam arm binary from previous job
-        uses: actions/download-artifact@v4
         with:
-          name: gleam-arm64
-          path: ./gleam-arm64
-
-      - name: Authenticate with GitHub container registry
-        uses: docker/login-action@v3
-        with:
-          registry: ghcr.io
-          username: lpil
-          password: ${{ secrets.CONTAINER_REGISTRY_PERSONAL_ACCESS_TOKEN }}
-
-      - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
+          sparse-checkout: |
+            .github/actions
+            containers
 
-      - name: Build and push
-        uses: docker/build-push-action@v6
+      - name: "Build & Push Container"
+        uses: "./.github/actions/build-container"
         with:
-          context: .
-          platforms: linux/amd64,linux/arm64
-          file: containers/${{ matrix.base-image }}.dockerfile
-          push: true
-          tags: ${{ steps.versions.outputs.container_tag }}
-            ghcr.io/${{ github.repository }}:nightly-${{ matrix.base-image }}
-          labels: |
-            org.opencontainers.image.title=gleam
-            org.opencontainers.image.url=https://gleam.run
-            org.opencontainers.image.source=https://github.com/gleam-lang/gleam
-            org.opencontainers.image.version=nightly-${{ matrix.base-image }}
-            org.opencontainers.image.licenses=Apache-2.0
+          version: nightly
+          release-id: ${{ needs.build-release.outputs.release-id }}
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 2b0ad216638..22b2502e5e1 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -8,6 +8,9 @@ env:
   CARGO_TERM_COLOR: always
   RUSTFLAGS: "-D warnings"
 
+permissions:
+  contents: write
+
 jobs:
   build-release:
     name: build-release
@@ -20,154 +23,55 @@ jobs:
           - x86_64-apple-darwin
           - aarch64-apple-darwin
           - x86_64-pc-windows-msvc
-        toolchain: [stable]
+        toolchain: [ stable ]
         include:
           - os: ubuntu-latest
             target: x86_64-unknown-linux-musl
-            binary: x86-64
+            expected-binary-architecture: x86-64
             cargo-tool: cross
           - os: ubuntu-latest
             target: aarch64-unknown-linux-musl
-            binary: aarch64
+            expected-binary-architecture: aarch64
             cargo-tool: cross
           # macos>=14 runs exclusively on aarch64 and will thus fail to execute properly for x64
           - os: macos-13
             target: x86_64-apple-darwin
-            binary: x86_64
+            expected-binary-architecture: x86_64
             cargo-tool: cargo
           - os: macos-latest
             target: aarch64-apple-darwin
-            binary: arm64
+            expected-binary-architecture: arm64
             cargo-tool: cargo
           - os: windows-latest
             target: x86_64-pc-windows-msvc
-            binary: x86-64
+            expected-binary-architecture: x86-64
             cargo-tool: cargo
+          - os: ubuntu-latest
+            target: wasm32-unknown-unknown
+            cargo-tool: wasm-pack
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4
 
-      - name: Install Rust toolchain
-        uses: dtolnay/rust-toolchain@stable
+      - name: "Build Archive"
+        id: build
+        uses: "./.github/actions/build-release"
         with:
+          version: ${{ github.ref_name }}
           toolchain: ${{ matrix.toolchain }}
           target: ${{ matrix.target }}
-
-      - name: Handle Rust dependencies caching
-        uses: Swatinem/rust-cache@v2
-        with:
-          key: v1-${{ matrix.target }}
-
-      - name: Build release binary
-        uses: clechasseur/rs-cargo@v3
-        with:
-          command: build
-          args: --release --target ${{ matrix.target }}
-          tool: ${{ matrix.cargo-tool }}
-
-      - name: Verify binary architecture
-        shell: bash
-        run: |
-          BINARY_PATH="target/${{ matrix.target }}/release/gleam"
-          if [[ "${{ matrix.target }}" == *"windows"* ]]; then
-            BINARY_PATH="${BINARY_PATH}.exe"
-          fi
-
-          if ! file -b "$BINARY_PATH" | grep -q "${{ matrix.binary }}"; then
-            echo "error: Architecture mismatch"
-            echo "Expected architecture: '${{ matrix.binary }}'"
-            echo "Found binary type: '$(file -b "$BINARY_PATH")'"
-            exit 1
-          fi
-          echo "ok: Architecture match"
-
-      - name: Build archive
-        shell: bash
-        run: |
-          VERSION="${GITHUB_REF#refs/tags/}"
-
-          if [ "${{ matrix.os }}" = "windows-latest" ]; then
-            ARCHIVE="gleam-$VERSION-${{ matrix.target }}.zip"
-            cp "target/${{ matrix.target }}/release/gleam.exe" "gleam.exe"
-            7z a "$ARCHIVE" "gleam.exe"
-            rm gleam.exe
-          else
-            ARCHIVE="gleam-$VERSION-${{ matrix.target }}.tar.gz"
-            cp "target/${{ matrix.target }}/release/gleam" "gleam"
-            tar -czvf "$ARCHIVE" "gleam"
-            rm gleam
-          fi
-
-          openssl dgst -r -sha256 -out "$ARCHIVE".sha256 "$ARCHIVE"
-          openssl dgst -r -sha512 -out "$ARCHIVE".sha512 "$ARCHIVE"
-          echo "ASSET=$ARCHIVE" >> $GITHUB_ENV
-
-      - name: Ensure binary successfully boots
-        shell: bash
-        run: |
-          case "${{ matrix.target }}" in
-            x86_64-pc-windows-msvc)
-              7z x "$ASSET"
-              ./gleam.exe --version ;;
-            aarch64*)
-              echo "We cannot test an ARM binary on a AMD64 runner" ;;
-            *)
-              tar -xvzf "$ASSET"
-              ./gleam --version ;;
-          esac
+          cargo-tool: ${{ matrix.cargo-tool }}
+          expected-binary-architecture: ${{ matrix.expected-binary-architecture }}
 
       - name: Upload release archive
+        id: release
         # https://github.com/softprops/action-gh-release/issues/445
         # uses: softprops/action-gh-release@v2
         uses: softprops/action-gh-release@0bd7e8b279c9b5b36661d552472fbbfe671fe26e
         with:
           draft: true
-          prerelease: false
-          fail_on_unmatched_files: true
-          files: |
-            ${{ env.ASSET }}
-            ${{ env.ASSET }}.sha256
-            ${{ env.ASSET }}.sha512
-
-  build-release-wasm:
-    name: build-release-wasm
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v4
-
-      - name: Install Rust toolchain
-        uses: actions-rs/toolchain@v1
-        with:
-          toolchain: stable
-          target: wasm32-unknown-unknown
-          profile: minimal
-          override: true
-
-      - name: Install wasm-pack
-        run: curl -sSL https://rustwasm.github.io/wasm-pack/installer/init.sh | sh
-
-      - name: Build wasm
-        run: wasm-pack build --release --target web compiler-wasm
-
-      - name: Build wasm archive
-        run: |
-          VERSION="${GITHUB_REF#refs/tags/}"
-          ARCHIVE="gleam-$VERSION-browser.tar.gz"
-
-          tar -C compiler-wasm/pkg/ -czvf $ARCHIVE .
-
-          openssl dgst -r -sha256 -out "$ARCHIVE".sha256 "$ARCHIVE"
-          openssl dgst -r -sha512 -out "$ARCHIVE".sha512 "$ARCHIVE"
-          echo "ASSET=$ARCHIVE" >> $GITHUB_ENV
-
-      - name: Upload release archive
-        uses: softprops/action-gh-release@v2
-        with:
-          draft: true
-          prerelease: false
+          prerelease: ${{ contains(github.ref_name, '-rc.') }}
           fail_on_unmatched_files: true
-          files: |
-            ${{ env.ASSET }}
-            ${{ env.ASSET }}.sha256
-            ${{ env.ASSET }}.sha512
+          files: "${{ steps.build.outputs.files }}"
+          generate_release_notes: true
+          tag_name: ${{ github.ref_name }}


Reviews:
user: login: maennchen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-21T22:29:54Z
user: login: maennchen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-21T22:29:58Z
user: login: lpil
body: Very nice! Thank you so much for doing this. I've left some questions inline.

Is there a repo where I can see it successfully running and test the output?


state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-23T23:43:40Z
user: login: maennchen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T11:06:29Z
user: login: maennchen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T11:14:48Z
user: login: maennchen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T11:15:34Z
user: login: maennchen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T11:18:07Z
user: login: lpil
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-02-24T11:37:24Z
user: login: maennchen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-02-24T11:40:31Z
user: login: lpil
body: Wonderful, thank you! This looks great to me. I've left a tiny naming related note, and then we can merge I think
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-20T12:36:49Z
user: login: maennchen
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T12:46:24Z
user: login: lpil
body: Thank you once again! And thanks for waiting with my slow review :)
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-20T12:47:34Z

Review comments:
diff_hunk: @@ -0,0 +1,133 @@
+name: "Build Gleam"
+description: "Build Gleam Release"
+inputs:
+  version:
+    description: "Build Version"
+    required: true
+  toolchain:
+    description: "Cargo Toolchain"
+    required: true
+  target:
+    description: "Cargo Installation Target"
+    required: true
+  cargo-tool:
+    description: "Cargo Tool" # TODO: What is this? describe...
path: .github/actions/build-release/action.yml
user: login: maennchen
body: TODO: Put a better description
created_at: 2025-02-21T22:29:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,133 @@
+name: "Build Gleam"
+description: "Build Gleam Release"
+inputs:
+  version:
+    description: "Build Version"
+    required: true
+  toolchain:
+    description: "Cargo Toolchain"
+    required: true
+  target:
+    description: "Cargo Installation Target"
+    required: true
+  cargo-tool:
+    description: "Cargo Tool" # TODO: What is this? describe...
+    required: true
+  binary:
+    description: "Binary" # TODO: What is this? describe...
path: .github/actions/build-release/action.yml
user: login: maennchen
body: TODO: Put a better description
created_at: 2025-02-21T22:29:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,133 @@
+name: "Build Gleam"
+description: "Build Gleam Release"
+inputs:
+  version:
+    description: "Build Version"
+    required: true
+  toolchain:
+    description: "Cargo Toolchain"
+    required: true
+  target:
+    description: "Cargo Installation Target"
+    required: true
+  cargo-tool:
+    description: "Cargo Tool" # TODO: What is this? describe...
+    required: true
+  binary:
+    description: "Binary" # TODO: What is this? describe...
path: .github/actions/build-release/action.yml
user: login: lpil
body: This is the expected binary architecture, used to verify the binary has been correctly produced. `expected-binary-architecture` or such would be a clearer name
created_at: 2025-02-23T23:30:03Z
author_association: MEMBER
diff_hunk: @@ -3,12 +3,52 @@ on:
   push:
     tags:
       - "v*"
+  workflow_dispatch: {}
+  schedule:
+    - cron: "45 0 * * *"
 
 env:
   CARGO_TERM_COLOR: always
   RUSTFLAGS: "-D warnings"
+  VERSION: "${{ github.event_name == 'push' && github.ref_name || 'nightly' }}"
+
+permissions:
+  contents: write
+  id-token: write
path: .github/workflows/release.yaml
user: login: lpil
body: Why are these two permissions here? I don't feel very comfortable giving a workflow commit or OIDC permissions.
created_at: 2025-02-23T23:35:02Z
author_association: MEMBER
diff_hunk: @@ -3,12 +3,52 @@ on:
   push:
     tags:
       - "v*"
+  workflow_dispatch: {}
+  schedule:
+    - cron: "45 0 * * *"
 
 env:
   CARGO_TERM_COLOR: always
   RUSTFLAGS: "-D warnings"
+  VERSION: "${{ github.event_name == 'push' && github.ref_name || 'nightly' }}"
+
+permissions:
+  contents: write
+  id-token: write
+  packages: write
 
 jobs:
+  prepare-nightly:
path: .github/workflows/release.yaml
user: login: lpil
body: Could you add a comment explaining what this job is for please 🙏 
created_at: 2025-02-23T23:35:24Z
author_association: MEMBER
diff_hunk: @@ -124,50 +113,103 @@ jobs:
           draft: true
           prerelease: false
           fail_on_unmatched_files: true
-          files: |
-            ${{ env.ASSET }}
-            ${{ env.ASSET }}.sha256
-            ${{ env.ASSET }}.sha512
+          files: "${{ steps.build.outputs.files }}"
 
-  build-release-wasm:
-    name: build-release-wasm
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: release-${{ matrix.target }}
+          path: "${{ steps.build.outputs.files }}"
+          overwrite: true
+
+  build-container-images:
+    name: build-container-images
+    needs: [ build-release ]
     runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'push' || (github.repository == 'gleam-lang/gleam' && needs.prepare-nightly.outputs.should-run != 'false') }}
+    strategy:
+      matrix:
+        base-image:
+          - scratch
+          - erlang
+          - erlang-slim
+          - erlang-alpine
+          - elixir
+          - elixir-slim
+          - elixir-alpine
+          - node
+          - node-slim
+          - node-alpine
path: .github/workflows/release.yaml
user: login: lpil
body: Currently containers are not created until after the release is published, but if I'm reading this right the containers would be published _before_ the release is, and would still be released even if the release was rejected.
created_at: 2025-02-23T23:38:00Z
author_association: MEMBER
diff_hunk: @@ -124,50 +113,103 @@ jobs:
           draft: true
           prerelease: false
           fail_on_unmatched_files: true
-          files: |
-            ${{ env.ASSET }}
-            ${{ env.ASSET }}.sha256
-            ${{ env.ASSET }}.sha512
+          files: "${{ steps.build.outputs.files }}"
 
-  build-release-wasm:
-    name: build-release-wasm
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: release-${{ matrix.target }}
+          path: "${{ steps.build.outputs.files }}"
+          overwrite: true
+
+  build-container-images:
+    name: build-container-images
+    needs: [ build-release ]
     runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'push' || (github.repository == 'gleam-lang/gleam' && needs.prepare-nightly.outputs.should-run != 'false') }}
+    strategy:
+      matrix:
+        base-image:
+          - scratch
+          - erlang
+          - erlang-slim
+          - erlang-alpine
+          - elixir
+          - elixir-slim
+          - elixir-alpine
+          - node
+          - node-slim
+          - node-alpine
+
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4
+        with:
+          sparse-checkout: containers
 
-      - name: Install Rust toolchain
-        uses: actions-rs/toolchain@v1
+      - name: Download Gleam release from previous job
+        uses: actions/download-artifact@v4
         with:
-          toolchain: stable
-          target: wasm32-unknown-unknown
-          profile: minimal
-          override: true
+          pattern: release-{x86_64-unknown-linux-musl,aarch64-unknown-linux-musl}
+
+      - name: "Unpack and rearrange release"
path: .github/workflows/release.yaml
user: login: lpil
body: What does this do?
created_at: 2025-02-23T23:40:00Z
author_association: MEMBER
diff_hunk: @@ -3,12 +3,52 @@ on:
   push:
     tags:
       - "v*"
+  workflow_dispatch: {}
+  schedule:
+    - cron: "45 0 * * *"
 
 env:
   CARGO_TERM_COLOR: always
   RUSTFLAGS: "-D warnings"
+  VERSION: "${{ github.event_name == 'push' && github.ref_name || 'nightly' }}"
+
+permissions:
+  contents: write
+  id-token: write
path: .github/workflows/release.yaml
user: login: maennchen
body: `id-token` was mistakenly added from the SBOM PR. I'll remove it.
`contents: write` is necessary to change release assets. It was already given before, just not explicit.
created_at: 2025-02-24T11:06:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,12 +3,52 @@ on:
   push:
     tags:
       - "v*"
+  workflow_dispatch: {}
+  schedule:
+    - cron: "45 0 * * *"
 
 env:
   CARGO_TERM_COLOR: always
   RUSTFLAGS: "-D warnings"
+  VERSION: "${{ github.event_name == 'push' && github.ref_name || 'nightly' }}"
+
+permissions:
+  contents: write
+  id-token: write
+  packages: write
 
 jobs:
+  prepare-nightly:
path: .github/workflows/release.yaml
user: login: maennchen
body: Added.
created_at: 2025-02-24T11:14:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,50 +113,103 @@ jobs:
           draft: true
           prerelease: false
           fail_on_unmatched_files: true
-          files: |
-            ${{ env.ASSET }}
-            ${{ env.ASSET }}.sha256
-            ${{ env.ASSET }}.sha512
+          files: "${{ steps.build.outputs.files }}"
 
-  build-release-wasm:
-    name: build-release-wasm
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: release-${{ matrix.target }}
+          path: "${{ steps.build.outputs.files }}"
+          overwrite: true
+
+  build-container-images:
+    name: build-container-images
+    needs: [ build-release ]
     runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'push' || (github.repository == 'gleam-lang/gleam' && needs.prepare-nightly.outputs.should-run != 'false') }}
+    strategy:
+      matrix:
+        base-image:
+          - scratch
+          - erlang
+          - erlang-slim
+          - erlang-alpine
+          - elixir
+          - elixir-slim
+          - elixir-alpine
+          - node
+          - node-slim
+          - node-alpine
+
     steps:
       - name: Checkout repository
         uses: actions/checkout@v4
+        with:
+          sparse-checkout: containers
 
-      - name: Install Rust toolchain
-        uses: actions-rs/toolchain@v1
+      - name: Download Gleam release from previous job
+        uses: actions/download-artifact@v4
         with:
-          toolchain: stable
-          target: wasm32-unknown-unknown
-          profile: minimal
-          override: true
+          pattern: release-{x86_64-unknown-linux-musl,aarch64-unknown-linux-musl}
+
+      - name: "Unpack and rearrange release"
path: .github/workflows/release.yaml
user: login: maennchen
body: Added an explanation. The approach might look a bit complicated, but makes more sense when additional files are used as with the SBoM PR.
created_at: 2025-02-24T11:15:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,50 +113,103 @@ jobs:
           draft: true
           prerelease: false
           fail_on_unmatched_files: true
-          files: |
-            ${{ env.ASSET }}
-            ${{ env.ASSET }}.sha256
-            ${{ env.ASSET }}.sha512
+          files: "${{ steps.build.outputs.files }}"
 
-  build-release-wasm:
-    name: build-release-wasm
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: release-${{ matrix.target }}
+          path: "${{ steps.build.outputs.files }}"
+          overwrite: true
+
+  build-container-images:
+    name: build-container-images
+    needs: [ build-release ]
     runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'push' || (github.repository == 'gleam-lang/gleam' && needs.prepare-nightly.outputs.should-run != 'false') }}
+    strategy:
+      matrix:
+        base-image:
+          - scratch
+          - erlang
+          - erlang-slim
+          - erlang-alpine
+          - elixir
+          - elixir-slim
+          - elixir-alpine
+          - node
+          - node-slim
+          - node-alpine
path: .github/workflows/release.yaml
user: login: maennchen
body: ## Old

If I understand how it works currently correctly, The release is created here:

https://github.com/gleam-lang/gleam/blob/d292718f34d5ce2edbb1ae704712f9cc46fbc14e/.github/workflows/release.yaml#L119-L130

Therefore, as soon as one of the native or WASM releases was successfully uploaded, the containers will be built.

## New

The `needs` ensures that the container jobs wait until all of the native / WASM releases are built:

https://github.com/gleam-lang/gleam/pull/4275/files#diff-e426ed45842837026e10e66af23d9c7077e89eacbe6958ce7cb991130ad05adaR130
created_at: 2025-02-24T11:18:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -124,50 +113,103 @@ jobs:
           draft: true
           prerelease: false
           fail_on_unmatched_files: true
-          files: |
-            ${{ env.ASSET }}
-            ${{ env.ASSET }}.sha256
-            ${{ env.ASSET }}.sha512
+          files: "${{ steps.build.outputs.files }}"
 
-  build-release-wasm:
-    name: build-release-wasm
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: release-${{ matrix.target }}
+          path: "${{ steps.build.outputs.files }}"
+          overwrite: true
+
+  build-container-images:
+    name: build-container-images
+    needs: [ build-release ]
     runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'push' || (github.repository == 'gleam-lang/gleam' && needs.prepare-nightly.outputs.should-run != 'false') }}
+    strategy:
+      matrix:
+        base-image:
+          - scratch
+          - erlang
+          - erlang-slim
+          - erlang-alpine
+          - elixir
+          - elixir-slim
+          - elixir-alpine
+          - node
+          - node-slim
+          - node-alpine
path: .github/workflows/release.yaml
user: login: lpil
body: That's still not the behaviour from before unfortunately. When the release workflow finishes it leaves a GH release in draft mode. I can then check this release, the assets, and anything else before publishing it, and publication then results in the container images being built and published. With this new workflow the container images would be published even if I reject the release and do not publish it.
created_at: 2025-02-24T11:37:24Z
author_association: MEMBER
diff_hunk: @@ -124,50 +113,103 @@ jobs:
           draft: true
           prerelease: false
           fail_on_unmatched_files: true
-          files: |
-            ${{ env.ASSET }}
-            ${{ env.ASSET }}.sha256
-            ${{ env.ASSET }}.sha512
+          files: "${{ steps.build.outputs.files }}"
 
-  build-release-wasm:
-    name: build-release-wasm
+      - name: Upload artifact
+        uses: actions/upload-artifact@v4
+        with:
+          name: release-${{ matrix.target }}
+          path: "${{ steps.build.outputs.files }}"
+          overwrite: true
+
+  build-container-images:
+    name: build-container-images
+    needs: [ build-release ]
     runs-on: ubuntu-latest
+    if: ${{ github.event_name == 'push' || (github.repository == 'gleam-lang/gleam' && needs.prepare-nightly.outputs.should-run != 'false') }}
+    strategy:
+      matrix:
+        base-image:
+          - scratch
+          - erlang
+          - erlang-slim
+          - erlang-alpine
+          - elixir
+          - elixir-slim
+          - elixir-alpine
+          - node
+          - node-slim
+          - node-alpine
path: .github/workflows/release.yaml
user: login: maennchen
body: Ah, I misunderstood how that worked before. Yes, it now works differently.
I'll take the PR back into draft state and fix this.
created_at: 2025-02-24T11:40:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -4,10 +4,15 @@ on:
     types:
       - "published"
 
+permissions:
+  packages: write
+
 jobs:
-  publish-container-images:
-    name: publish-container-images
+  build-container-images:
path: .github/workflows/release-containers.yaml
user: login: lpil
body: Could we revert to the previous name of publish-container-images please, and use that for the action too? The important outcome is that it publishes images, so I want that to be immediately obvious. And as a pedantic nit it builds images, not containers.
created_at: 2025-03-20T12:35:45Z
author_association: MEMBER
diff_hunk: @@ -4,10 +4,15 @@ on:
     types:
       - "published"
 
+permissions:
+  packages: write
+
 jobs:
-  publish-container-images:
-    name: publish-container-images
+  build-container-images:
path: .github/workflows/release-containers.yaml
user: login: maennchen
body: That was left over form my wrong approach before. Changed back to the original name :)
created_at: 2025-03-20T12:46:23Z
author_association: CONTRIBUTOR

PR comments:
user: login: lpil
created_at: 2025-02-22T21:55:02Z
body: Is this ready for a review? :)
author_association: MEMBER
user: login: maennchen
created_at: 2025-02-23T03:55:11Z
body: @lpil There’s one TODO open, where I could use some input / insights.

Besides that, yes.
author_association: CONTRIBUTOR
user: login: maennchen
created_at: 2025-02-24T11:38:44Z
body: @lpil 

> Is there a repo where I can see it successfully running and test the output?

Yes:

* Example Release (`vjm20`): https://github.com/maennchen/gleam/releases/tag/untagged-b42e320abb87e6969c11
* Example CI: https://github.com/maennchen/gleam/actions/runs/13496871664
author_association: CONTRIBUTOR
user: login: lpil
created_at: 2025-03-04T12:43:58Z
body: Is this ready for a review? Undraft if so! 🙏 
author_association: MEMBER
user: login: maennchen
created_at: 2025-03-04T13:45:26Z
body: @lpil I haven’t gotten around to it yet. Probably later this week or start of the next.
author_association: CONTRIBUTOR
user: login: maennchen
created_at: 2025-03-12T14:36:52Z
body: @lpil Ready for review again. I linked the CI runs & Releases in the PR body.
author_association: CONTRIBUTOR
user: login: maennchen
created_at: 2025-03-20T12:48:01Z
body: @lpil No worries, I have lots of other things going on as well :)
author_association: CONTRIBUTOR
user: login: lpil
created_at: 2025-03-21T09:29:11Z
body: Hi @maennchen ! The nightly build failed: <https://github.com/gleam-lang/gleam/actions/runs/13982471191/job/39150515439>

Is there something we still need to do?
author_association: MEMBER