Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 95 additions & 27 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,62 +36,130 @@
# TODO: To be implemented
# test-job:
# uses: ./.github/workflows/ci.yml
build-job:
# TODO: To be implemented
# needs: test-job
setup:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Checkout code
uses: actions/checkout@v5
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v1
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Missing dependency installation in setup job

The setup job removed the bun install step that was present in the old workflow. The check-bun-node.ts script imports @nodevu/core from package.json dependencies, which won't be available without installing dependencies first. This causes the script to fail when running --cleanup or --matrix commands in subsequent steps.

Fix in Cursor Fix in Web


- uses: oven-sh/setup-bun@635640504f6d7197d3bb29876a652f671028dc97
- run: bun install
- name: Download versions.json
run: gh release download versions -p versions.json || echo "{}" > versions.json
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fallback echo "{}" > versions.json when the release doesn't exist creates an empty JSON object, which will cause multiple downstream issues:

  1. check-bun-node.ts will fail when accessing versionsJson.nodejs[release.major] (see related comment)
  2. build_updated.sh will fail when trying to extract codename and latest_node_major from the JSON
  3. All versions will be considered "new" on first run

Consider creating a proper initial structure with empty nodejs and bun objects:

gh release download versions -p versions.json || echo '{"bun":{},"nodejs":{}}' > versions.json
Suggested change
run: gh release download versions -p versions.json || echo "{}" > versions.json
run: gh release download versions -p versions.json || echo '{"bun":{},"nodejs":{}}' > versions.json

Copilot uses AI. Check for mistakes.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Check for new releases of nodejs and bun
if: ${{ inputs.nodejs-version == '' && inputs.bun-versions == '' && inputs.distros == '' }}
- name: Cleanup unsupported versions
run: |
set -e
echo "NODE_VERSIONS_TO_BUILD=$(bun run check-bun-node.ts --node ${{ env.NODE_MAJOR_VERSIONS_TO_CHECK }})" >> $GITHUB_ENV
echo "BUN_VERSIONS_TO_BUILD=$(bun run check-bun-node.ts --bun ${{ env.BUN_TAGS_TO_CHECK }})" >> $GITHUB_ENV
bun run check-bun-node.ts --cleanup --generate
if [[ `git status --porcelain` ]]; then
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add .
git commit -m "chore: cleanup unsupported versions"
git push
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The cleanup step commits and pushes directly to the main branch without creating a pull request or obtaining approval. This automated cleanup could accidentally delete version folders that are still in use if the Node.js version detection has issues or if the external API returns incorrect data.

Consider one of the following safer approaches:

  1. Create a pull request for the cleanup changes instead of direct push
  2. Add a dry-run mode that logs what would be deleted without actually deleting
  3. Require manual workflow dispatch approval for cleanup operations
  4. Add better validation that the versions being deleted are truly EOL
Suggested change
git push
# Instead of pushing directly, create a pull request for the cleanup changes
gh pr create --title "chore: cleanup unsupported versions" --body "Automated cleanup of unsupported versions." --base main --head ${{ github.ref_name }}

Copilot uses AI. Check for mistakes.
fi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Missing git pull creates race condition risk

The cleanup step commits and pushes changes without pulling first. The previous workflow included a git pull -r step before pushing. If multiple workflow runs execute concurrently (e.g., scheduled run and manual trigger), the push operation will fail when the remote has been updated by another run, causing the cleanup step to error.

Fix in Cursor Fix in Web

Comment on lines +52 to +61
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cleanup step commits and pushes changes to the repository during the workflow. This can cause race conditions if multiple workflow runs happen simultaneously (e.g., scheduled and manual triggers). Additionally, pushing commits from within a workflow can trigger new workflow runs, potentially causing an infinite loop if not properly guarded.

Consider either:

  1. Using a separate workflow run for cleanup that runs less frequently
  2. Using branch protection rules or checking if changes were actually made to unsupported versions
  3. Using a lock mechanism to prevent concurrent cleanup operations

Copilot uses AI. Check for mistakes.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Generate Matrix
id: set-matrix
run: |
MATRIX=$(bun run check-bun-node.ts --matrix)
echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
env:
BUN_TAGS_TO_CHECK: ${{ env.BUN_TAGS_TO_CHECK }}
DISTROS: ${{ env.DISTROS }}
NODE_MAJOR_VERSIONS_TO_CHECK: ${{ env.NODE_MAJOR_VERSIONS_TO_CHECK }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Workflow manual trigger inputs completely ignored

The workflow defines manual trigger inputs for bun-versions, nodejs-version, and distros, but these inputs are never referenced in the new implementation. The generateMatrix function only reads from environment variables, not workflow inputs. This breaks manual workflow dispatch functionality where users expect to build specific versions by providing inputs, as was possible in the previous version that used ${{ env.NODE_VERSIONS_TO_BUILD || inputs.nodejs-version }}.

Fix in Cursor Fix in Web

Comment on lines +72 to +73
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The workflow defines workflow_dispatch inputs for bun-versions, nodejs-version, and distros (lines 8-19), but these inputs are not actually used in the new matrix-based workflow. The setup job generates the matrix dynamically from environment variables and doesn't consider the manual inputs.

This means manual workflow triggers with custom versions will be ignored. If this is intentional (forcing all builds to go through the automatic detection), the inputs should be removed from the workflow. Otherwise, the matrix generation logic needs to incorporate these inputs when provided.

Suggested change
DISTROS: ${{ env.DISTROS }}
NODE_MAJOR_VERSIONS_TO_CHECK: ${{ env.NODE_MAJOR_VERSIONS_TO_CHECK }}
DISTROS: ${{ github.event.inputs.distros != '' && github.event.inputs.distros || env.DISTROS }}
NODE_MAJOR_VERSIONS_TO_CHECK: ${{ env.NODE_MAJOR_VERSIONS_TO_CHECK }}
NODE_VERSIONS_TO_BUILD: ${{ github.event.inputs.nodejs-version != '' && github.event.inputs.nodejs-version || env.NODE_VERSIONS_TO_BUILD }}
BUN_VERSIONS_TO_BUILD: ${{ github.event.inputs['bun-versions'] != '' && github.event.inputs['bun-versions'] || env.BUN_VERSIONS_TO_BUILD }}

Copilot uses AI. Check for mistakes.

build:
needs: setup
runs-on: ubuntu-latest
if: ${{ fromJson(needs.setup.outputs.matrix).include[0] }}
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if: ${{ fromJson(needs.setup.outputs.matrix).include[0] }} will fail if the matrix JSON is malformed or if accessing .include[0] on an empty array. This can cause the workflow to fail with a cryptic error instead of gracefully skipping the build job.

Use a more robust condition:

if: ${{ needs.setup.outputs.matrix != '' && needs.setup.outputs.matrix != '{"include":[]}' }}

Or better yet, add a dedicated output flag in the setup job to indicate whether builds are needed.

Suggested change
if: ${{ fromJson(needs.setup.outputs.matrix).include[0] }}
if: ${{ needs.setup.outputs.matrix != '' && needs.setup.outputs.matrix != '{"include":[]}' }}

Copilot uses AI. Check for mistakes.
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
steps:
- uses: actions/checkout@v5

- name: Download versions.json
run: gh release download versions -p versions.json || echo "{}" > versions.json
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Setup Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}

- uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08
name: Build and push Docker images
with:
timeout_minutes: 120
max_attempts: 3
retry_on: error
command: ./build_updated.sh
command: ./build_single.sh --bun "${{ matrix.bun_version }}" --node "${{ matrix.node_version }}" --distro "${{ matrix.distro }}"
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing executable permission. The script is being called directly with ./build_single.sh but there's no step to make it executable. While this might work if the file is already committed with executable permissions, it's safer to ensure it explicitly in the workflow.

Add before the build step:

- name: Make build script executable
  run: chmod +x build_single.sh

Copilot uses AI. Check for mistakes.
env:
REGISTRY: ${{ env.REGISTRY }}
PLATFORMS: ${{ env.PLATFORMS }}
NODE_VERSIONS_TO_BUILD: ${{ env.NODE_VERSIONS_TO_BUILD || inputs.nodejs-version }}
BUN_VERSIONS_TO_BUILD: ${{ env.BUN_VERSIONS_TO_BUILD || inputs.bun-versions }}
DISTROS: ${{ env.DISTROS || inputs.distros }}

- name: Commit changes
run: ./commit_changes.sh
- name: Upload success artifact
uses: actions/upload-artifact@v4
with:
name: build-success-${{ matrix.bun_version }}-${{ matrix.node_version }}-${{ matrix.distro }}
path: build_success.json

update-release:
Comment on lines 76 to 125

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}

Copilot Autofix

AI 9 days ago

To fix this problem, add an explicit permissions block to the workflow configuration. Assign the minimal permissions required for the workflow/features used in each job. The workflow performs a variety of operations:

  • Uses gh CLI in setup, update-release (e.g. downloading/creating/uploading releases, which require contents write),
  • Commits and pushes in setup (requires contents: write),
  • Uses actions/checkout, uploads and downloads artifacts, but does not write to the repository in build or rerun-failed-jobs.

Therefore, set contents: write for the jobs that use gh release commands and/or push to the repo, and set contents: read for jobs that only require read access (like build and rerun-failed-jobs).
For clarity and security, add a permissions: block to each of the four jobs:

  • setup: contents: write
  • build: contents: read
  • update-release: contents: write
  • rerun-failed-jobs: contents: read
    Perform these additions at the top of each job definition in the file.

Suggested changeset 1
.github/workflows/release.yml

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -37,6 +37,8 @@
   # test-job:
   #   uses: ./.github/workflows/ci.yml
   setup:
+    permissions:
+      contents: write
     runs-on: ubuntu-latest
     outputs:
       matrix: ${{ steps.set-matrix.outputs.matrix }}
@@ -74,6 +76,8 @@
 
   build:
     needs: setup
+    permissions:
+      contents: read
     runs-on: ubuntu-latest
     if: ${{ fromJson(needs.setup.outputs.matrix).include[0] }}
     strategy:
@@ -115,6 +119,8 @@
 
   update-release:
     needs: build
+    permissions:
+      contents: write
     runs-on: ubuntu-latest
     if: always() && needs.build.result == 'success'
     steps:
@@ -158,6 +164,8 @@
           GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 
   rerun-failed-jobs:
+    permissions:
+      contents: read
     runs-on: ubuntu-latest
     needs: [build]
     if: failure()
EOF
@@ -37,6 +37,8 @@
# test-job:
# uses: ./.github/workflows/ci.yml
setup:
permissions:
contents: write
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
@@ -74,6 +76,8 @@

build:
needs: setup
permissions:
contents: read
runs-on: ubuntu-latest
if: ${{ fromJson(needs.setup.outputs.matrix).include[0] }}
strategy:
@@ -115,6 +119,8 @@

update-release:
needs: build
permissions:
contents: write
runs-on: ubuntu-latest
if: always() && needs.build.result == 'success'
steps:
@@ -158,6 +164,8 @@
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

rerun-failed-jobs:
permissions:
contents: read
runs-on: ubuntu-latest
needs: [build]
if: failure()
Copilot is powered by AI and may make mistakes. Always verify output.
needs: build
runs-on: ubuntu-latest
if: always() && needs.build.result == 'success'
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Matrix job success check prevents partial updates

The update-release job checks needs.build.result == 'success', but since build is a matrix job, this requires ALL matrix jobs to succeed. If any single build fails (e.g., one distro out of 12 combinations), the entire result is failure, preventing the update-release job from running. This means successful builds won't update versions.json in the release, losing track of which versions were actually built.

Fix in Cursor Fix in Web

Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if: always() && needs.build.result == 'success' is logically contradictory. If any build job in the matrix fails, needs.build.result will be either 'failure' or 'cancelled', not 'success'. This means the update-release job will never run if any build fails, even though some builds may have succeeded.

If you want to update versions.json with partial successes, use:

if: always() && contains(needs.build.result, 'success')

Or if you only want to proceed when ALL builds succeed:

if: needs.build.result == 'success'

The always() is redundant in the latter case.

Suggested change
if: always() && needs.build.result == 'success'
if: always() && contains(needs.build.result, 'success')

Copilot uses AI. Check for mistakes.
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v1

- name: Download versions.json
run: gh release download versions -p versions.json || echo "{}" > versions.json
env:
BUN_VERSIONS_TO_BUILD: ${{ env.BUN_VERSIONS_TO_BUILD }}
NODE_VERSIONS_TO_BUILD: ${{ env.NODE_VERSIONS_TO_BUILD }}
DISTROS: ${{ env.DISTROS }}
- name: Pull changes
run: git pull -r
- name: Push changes
uses: ad-m/github-push-action@77c5b412c50b723d2a4fbc6d71fb5723bcd439aa
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Download all artifacts
uses: actions/download-artifact@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
pattern: build-success-*
merge-multiple: true
path: updates

- name: Update versions.json
run: |
# Merge all JSON fragments into versions.json
# We iterate over all files in updates/ and merge them using jq
# Note: This is a simple merge. If multiple builds update the same key, last one wins (which is fine as they should be identical for the same version).

for file in updates/*.json; do
if [ -f "$file" ]; then
# Merge nodejs versions
# We need to be careful not to overwrite the whole object if we only have partial updates.
# jq's * operator merges objects recursively.
jq -s '.[0] * .[1]' versions.json "$file" > versions.json.tmp && mv versions.json.tmp versions.json
fi
done
Comment on lines +142 to +149
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The merge logic in the Update versions.json step doesn't handle the case where no build artifacts exist (e.g., if all builds failed or the matrix was empty). The glob pattern updates/*.json will fail to match any files, causing the loop to execute once with file set to the literal string "updates/*.json".

Add a check to handle this case:

shopt -s nullglob  # Prevent glob from matching itself if no files found
for file in updates/*.json; do
  if [ -f "$file" ]; then
    jq -s '.[0] * .[1]' versions.json "$file" > versions.json.tmp && mv versions.json.tmp versions.json
  fi
done

# Check if any updates were applied
if [ ! -f versions.json.tmp ]; then
  echo "No build artifacts found, skipping versions.json update"
fi

Copilot uses AI. Check for mistakes.

cat versions.json

- name: Upload versions.json
run: |
gh release create versions versions.json --title "Versions State" --notes "Stores the current versions.json state" || \
gh release upload versions versions.json --clobber
Comment on lines +154 to +156
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gh release create command will fail if a release named "versions" already exists (which it will after the first run). The fallback with || will then try to upload, but the error from the first command might cause confusion in logs.

Additionally, using a release tag named "versions" is unconventional and might conflict with actual version tags in the future. Consider using a more specific tag like "state" or "metadata".

Improve the logic:

# Check if release exists first
if gh release view versions >/dev/null 2>&1; then
  gh release upload versions versions.json --clobber
else
  gh release create versions versions.json --title "Versions State" --notes "Stores the current versions.json state"
fi

Copilot uses AI. Check for mistakes.
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

rerun-failed-jobs:

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
runs-on: ubuntu-latest
needs: [build-job]
needs: [build]
if: failure()
steps:
- name: Rerun failed jobs in the current workflow
Expand Down
144 changes: 144 additions & 0 deletions build_single.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#!/bin/bash

# Exit on error
set -e

# Logging function
log() {

Check warning on line 7 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxp0&open=AZqvsIsgWG7wpWAYVxp0&pullRequest=45
echo "[$(date +'%Y-%m-%dT%H:%M:%S%z')] $@"
}

# Retry function
retry() {
local retries=${RETRIES:-3}
local count=0
until "$@"; do
exit_code=$?
count=$((count + 1))
if [ $count -lt $retries ]; then

Check failure on line 18 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxpr&open=AZqvsIsgWG7wpWAYVxpr&pullRequest=45
log "Retrying ($count/$retries)..."
sleep 5
else
log "Failed after $count attempts."
return $exit_code
fi
done
return 0
}

# Parse arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
--bun) BUN_VERSION="$2"; shift ;;
--node) NODE_VERSION="$2"; shift ;;
--distro) DISTRO="$2"; shift ;;
*) echo "Unknown parameter passed: $1"; exit 1 ;;
esac
shift
done

if [ -z "$BUN_VERSION" ] || [ -z "$NODE_VERSION" ] || [ -z "$DISTRO" ]; then

Check failure on line 40 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxpu&open=AZqvsIsgWG7wpWAYVxpu&pullRequest=45

Check failure on line 40 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxpt&open=AZqvsIsgWG7wpWAYVxpt&pullRequest=45

Check failure on line 40 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxps&open=AZqvsIsgWG7wpWAYVxps&pullRequest=45
echo "Usage: $0 --bun <version> --node <version> --distro <distro>"
exit 1
fi

log "Building image for Bun version $BUN_VERSION, Node version $NODE_VERSION, Distro $DISTRO"

# Read versions.json for codename lookup (optional, but good for tagging)
# If versions.json is not present, we might miss some tags, but the matrix generation should have ensured we have what we need.
# Actually, we need versions.json to know the codename (e.g. "Iron") and to check if it's "latest".
# We can expect versions.json to be present in the working directory (downloaded by workflow).

if [ -f "versions.json" ]; then

Check failure on line 52 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxpv&open=AZqvsIsgWG7wpWAYVxpv&pullRequest=45
json_data=$(cat versions.json)
else
json_data="{}"
fi

REGISTRY=${REGISTRY:-imbios}
PLATFORMS=${PLATFORMS:-linux/amd64,linux/arm64}

generate_tags() {

Check warning on line 61 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxp1&open=AZqvsIsgWG7wpWAYVxp1&pullRequest=45
local bun_version=$1
local node_version=$2
local distro=$3

local node_major=${node_version%%.*}
local node_minor=${node_version%.*}
local bun_major=${bun_version%%.*}
local bun_minor=${bun_version%.*}

local is_canary=false
if [[ $bun_version == *"-canary"* ]]; then
is_canary=true
bun_version="canary"
fi

echo "$REGISTRY/bun-node:${bun_version}-${node_version}-${distro}"

if [ $is_canary == false ]; then

Check failure on line 79 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxpw&open=AZqvsIsgWG7wpWAYVxpw&pullRequest=45
echo "$REGISTRY/bun-node:${bun_minor}-${node_version}-${distro}"
echo "$REGISTRY/bun-node:${bun_major}-${node_version}-${distro}"
echo "$REGISTRY/bun-node:${bun_version}-${node_minor}-${distro}"
echo "$REGISTRY/bun-node:${bun_version}-${node_major}-${distro}"
elif [[ $bun_version == "canary" ]]; then
echo "$REGISTRY/bun-node:canary-${node_minor}-${distro}"
echo "$REGISTRY/bun-node:canary-${node_major}-${distro}"
fi

local codename=$(echo "${json_data}" | jq -r ".nodejs.\"${node_major}\".name")
if [ "$codename" != "null" ]; then

Check failure on line 90 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxpx&open=AZqvsIsgWG7wpWAYVxpx&pullRequest=45
echo "$REGISTRY/bun-node:${bun_version}-${codename}-${distro}"
if [[ $is_canary == false ]]; then
echo "$REGISTRY/bun-node:latest-${codename}-${distro}"
fi
fi

if [[ $is_canary == false ]]; then
echo "$REGISTRY/bun-node:latest-${node_version}-${distro}"
echo "$REGISTRY/bun-node:latest-${node_major}-${distro}"
fi

# Latest tag logic
# We rely on the caller (workflow) or check versions.json to see if this is the latest Node version.
local latest_node_major=$(echo "${json_data}" | jq -r '.nodejs | keys | map(tonumber) | max')

if [[ $is_canary == false && "$node_major" == "$latest_node_major" && $distro == "debian" ]]; then
echo "$REGISTRY/bun-node:latest"
fi
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Empty versions.json causes jq error for latest tag

When versions.json is empty (first run scenario), the jq command .nodejs | keys | map(tonumber) | max fails because .nodejs returns null on {}. This sets latest_node_major to empty, preventing the latest tag from being generated. The old implementation hardcoded the comparison to "20", avoiding this dependency on versions.json contents, while the new dynamic approach breaks on empty input.

Fix in Cursor Fix in Web


if [[ $is_canary == false ]]; then
echo "$REGISTRY/bun-node:${node_major}-${distro}"
fi
}

tag_distro=$DISTRO
if [ "$DISTRO" == "debian-slim" ]; then

Check failure on line 116 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxpy&open=AZqvsIsgWG7wpWAYVxpy&pullRequest=45
tag_distro="slim"
fi

tags=($(generate_tags "$BUN_VERSION" "$NODE_VERSION" "$tag_distro"))
image_name="$REGISTRY/bun-node:${BUN_VERSION}-${NODE_VERSION}-${tag_distro}"

node_major=${NODE_VERSION%%.*}

for tag in "${tags[@]}"; do
log "Tagging $image_name as $tag"
retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" -t "$image_name" -t "$tag" "./src/base/${node_major}/${DISTRO}" --push --provenance=mode=max

if [ "$DISTRO" == "alpine" ]; then

Check failure on line 129 in build_single.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=ImBIOS_bun-node&issues=AZqvsIsgWG7wpWAYVxpz&open=AZqvsIsgWG7wpWAYVxpz&pullRequest=45
log "Building and Tagging Alpine image with Git"
retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" -t "$image_name-git" -t "$tag-git" "./src/git/${node_major}/${DISTRO}" --push --provenance=mode=max
fi
done
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Docker image rebuilt for every tag

The loop rebuilds the same Docker image from scratch for each tag instead of building once with all tags. With potentially 10+ tags per image, this causes the same image to be built 10+ times, significantly increasing build time and resource usage. The docker buildx build command accepts multiple -t flags, so all tags should be collected and passed in a single build command outside the loop.

Fix in Cursor Fix in Web


Comment on lines +125 to +134
Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docker build command is executed inside a for loop that iterates over all tags, causing the same image to be built multiple times (once for each tag). This is inefficient and wastes CI time.

The build should happen only once with all -t flags passed in a single command. Consider restructuring like this:

# Build all tags in a single command
tag_args=""
for tag in "${tags[@]}"; do
  tag_args="$tag_args -t $tag"
done

log "Building and pushing image with tags: ${tags[*]}"
retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" $tag_args "./src/base/${node_major}/${DISTRO}" --push --provenance=mode=max

if [ "$DISTRO" == "alpine" ]; then
  log "Building and Tagging Alpine image with Git"
  git_tag_args=""
  for tag in "${tags[@]}"; do
    git_tag_args="$git_tag_args -t $tag-git"
  done
  retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" $git_tag_args "./src/git/${node_major}/${DISTRO}" --push --provenance=mode=max
fi
Suggested change
for tag in "${tags[@]}"; do
log "Tagging $image_name as $tag"
retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" -t "$image_name" -t "$tag" "./src/base/${node_major}/${DISTRO}" --push --provenance=mode=max
if [ "$DISTRO" == "alpine" ]; then
log "Building and Tagging Alpine image with Git"
retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" -t "$image_name-git" -t "$tag-git" "./src/git/${node_major}/${DISTRO}" --push --provenance=mode=max
fi
done
# Build all tags in a single command
tag_args="-t \"$image_name\""
for tag in "${tags[@]}"; do
tag_args="$tag_args -t \"$tag\""
done
log "Building and pushing image with tags: ${tags[*]}"
retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" $tag_args "./src/base/${node_major}/${DISTRO}" --push --provenance=mode=max
if [ "$DISTRO" == "alpine" ]; then
log "Building and Tagging Alpine image with Git"
git_tag_args="-t \"$image_name-git\""
for tag in "${tags[@]}"; do
git_tag_args="$git_tag_args -t \"$tag-git\""
done
retry docker buildx build --sbom=true --provenance=true --platform "$PLATFORMS" $git_tag_args "./src/git/${node_major}/${DISTRO}" --push --provenance=mode=max
fi

Copilot uses AI. Check for mistakes.
# Output success JSON fragment for aggregation
# We need to update versions.json with the new versions.
# We output a JSON file that the workflow can pick up.
bun_tag="latest"
if [[ $BUN_VERSION == *"-canary"* ]]; then
bun_tag="canary"
fi

# Create a partial JSON update
echo "{\"nodejs\": {\"$node_major\": {\"version\": \"v$NODE_VERSION\"}}, \"bun\": {\"$bun_tag\": \"v$BUN_VERSION\"}}" > build_success.json
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Codename missing from build success JSON output

The build_success.json output only includes the version field for Node.js entries but omits the name field containing the codename (like "iron" or "jod"). When a new Node major version is built for the first time, the merged versions.json won't have the codename, causing tags like latest-iron-debian to be skipped during subsequent builds since generate_tags checks for non-null codenames before creating those tags.

Fix in Cursor Fix in Web

Copy link

Copilot AI Nov 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build_success.json output is missing the Node.js codename field, which is present in the original versions.json structure. This will cause the merged versions.json to lose codename information (e.g., "iron", "jod", "krypton").

The output should include the codename:

codename=$(echo "${json_data}" | jq -r ".nodejs.\"${node_major}\".name")
if [ "$codename" != "null" ] && [ -n "$codename" ]; then
  echo "{\"nodejs\": {\"$node_major\": {\"name\": \"$codename\", \"version\": \"v$NODE_VERSION\"}}, \"bun\": {\"$bun_tag\": \"v$BUN_VERSION\"}}" > build_success.json
else
  echo "{\"nodejs\": {\"$node_major\": {\"version\": \"v$NODE_VERSION\"}}, \"bun\": {\"$bun_tag\": \"v$BUN_VERSION\"}}" > build_success.json
fi
Suggested change
echo "{\"nodejs\": {\"$node_major\": {\"version\": \"v$NODE_VERSION\"}}, \"bun\": {\"$bun_tag\": \"v$BUN_VERSION\"}}" > build_success.json
# Extract codename from versions.json if available
if [ -f versions.json ]; then
json_data=$(cat versions.json)
codename=$(echo "${json_data}" | jq -r ".nodejs.\"${node_major}\".name")
else
codename=""
fi
if [ "$codename" != "null" ] && [ -n "$codename" ]; then
echo "{\"nodejs\": {\"$node_major\": {\"name\": \"$codename\", \"version\": \"v$NODE_VERSION\"}}, \"bun\": {\"$bun_tag\": \"v$BUN_VERSION\"}}" > build_success.json
else
echo "{\"nodejs\": {\"$node_major\": {\"version\": \"v$NODE_VERSION\"}}, \"bun\": {\"$bun_tag\": \"v$BUN_VERSION\"}}" > build_success.json
fi

Copilot uses AI. Check for mistakes.
Loading
Loading