From 01319d72c1e3e820e0d4614d289853234d83b14e Mon Sep 17 00:00:00 2001 From: Hedi Nasr Date: Tue, 27 Oct 2026 08:42:53 +0100 Subject: [PATCH] BUILD-13251: Add config-bun action Configure Bun to resolve npm packages from the Repox selected by repox-url (SaaS on GitHub-hosted runners, production Edge on self-hosted runners) with the matching Vault reader token, exported through BUN_CONFIG_REGISTRY and BUN_CONFIG_TOKEN. Bun keeps absolute tarball URLs in bun.lock, so by default the action makes Repox tarball URLs registry-relative and warns when it had to rewrite the lockfile. It also caches the Bun install cache. --- .shellspec | 2 +- README.md | 65 ++++++++++++++++++++++++++++++ config-bun/action.yml | 86 ++++++++++++++++++++++++++++++++++++++++ config-bun/bun_config.sh | 40 +++++++++++++++++++ sonar-project.properties | 2 +- spec/config-bun_spec.sh | 58 +++++++++++++++++++++++++++ 6 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 config-bun/action.yml create mode 100755 config-bun/bun_config.sh create mode 100644 spec/config-bun_spec.sh diff --git a/.shellspec b/.shellspec index 407bf88c..21a664a7 100644 --- a/.shellspec +++ b/.shellspec @@ -1,5 +1,5 @@ # kcov (coverage) options ---kcov-options "--include-pattern=build-poetry,config-uv,config-poetry,get-build-number,pr_cleanup,promote,build-gradle,config-maven,build-maven,config-npm,build-npm,build-yarn,shared,config-gradle,config-pip,update-release-channel,report-ci-metrics,config-nuget" +--kcov-options "--include-pattern=build-poetry,config-uv,config-poetry,get-build-number,pr_cleanup,promote,build-gradle,config-maven,build-maven,config-npm,build-npm,build-yarn,shared,config-gradle,config-pip,update-release-channel,report-ci-metrics,config-nuget,config-bun" # --kcov-options "--exclude-pattern=.github,.idea,.git" # define minimum coverage (fail otherwise) diff --git a/README.md b/README.md index 423008c3..619f49eb 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ These badges show the status of workflows in dummy repositories that use (or sho - [`build-yarn`](#build-yarn) - [`config-pip`](#config-pip) - [`config-uv`](#config-uv) +- [`config-bun`](#config-bun) - [`config-nuget`](#config-nuget) - [`promote`](#promote) - [`pr_cleanup`](#pr_cleanup) @@ -1482,6 +1483,70 @@ See also [`get-build-number`](#get-build-number) output environment variables. --- +## `config-bun` + +Configure Bun to resolve npm packages from Repox with the matching reader token, and cache Bun installs. + +The action sets [Bun's registry environment variables](https://bun.sh/docs/pm/cli/install). They replace the default +registry and token configured in `bunfig.toml` or `.npmrc`; scoped registries are not changed. + +Bun downloads each locked package from the tarball URL written in `bun.lock`, not from the configured registry. Commit a +`bun.lock` resolved through the same Repox as the selected `repox-url`. + +The action does not install Bun or dependencies, and does not manage the build number. + +### Requirements + +#### Required GitHub Permissions + +- `id-token: write` + +#### Required Vault Permissions + +- `public-reader` or `private-reader`: Artifactory role for reading dependencies + +#### Other Dependencies + +The `bun` tool must be pre-installed. Use of `mise` is recommended. + +### Usage + +```yaml +permissions: + id-token: write +steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - uses: step-security/mise-action@a225bbd7c202ddf57dd76dff22bb7992f6314173 # v5.1.2 + - uses: SonarSource/ci-github-actions/config-bun@v2 + - run: bun ci +``` + +### Inputs + +| Input | Description | Default | +|---------------------------|-----------------------------------------------------------------------------|----------------------------------------------------------------------| +| `working-directory` | Relative path under github.workspace containing `bun.lock` | `.` | +| `artifactory-reader-role` | Suffix for the Artifactory reader role in Vault | `private-reader` for private repos, `public-reader` for public repos | +| `repox-url` | URL for Repox | Runner-dependent (see Edge section) | +| `disable-caching` | Whether to disable Bun install caching entirely | `false` | + +### Outputs + +| Output | Description | +|------------|-------------------------------------------------------------------------------------------------| +| `registry` | The npm registry URL selected for Bun. Also set as environment variable `BUN_CONFIG_REGISTRY` | + +### Output Environment Variables + +| Environment Variable | Description | +|----------------------------|---------------------------------------------------| +| `BUN_CONFIG_REGISTRY` | npm registry URL under the selected Repox | +| `BUN_CONFIG_TOKEN` | Repox reader access token for that registry | +| `ARTIFACTORY_USERNAME` | Repox reader username | +| `ARTIFACTORY_ACCESS_TOKEN` | Repox reader access token | + +--- + ## `config-nuget` Configure NuGet authentication and resolve the Repox package sources of NuGet configuration files through `repox-url`. diff --git a/config-bun/action.yml b/config-bun/action.yml new file mode 100644 index 00000000..da34fe47 --- /dev/null +++ b/config-bun/action.yml @@ -0,0 +1,86 @@ +--- +name: Config Bun +description: GitHub Action to configure Bun dependency resolution with Artifactory authentication and caching +inputs: + working-directory: + description: Relative path under github.workspace containing bun.lock + default: . + artifactory-reader-role: + description: Suffix for the Artifactory reader role in Vault. Defaults to `private-reader` for private repositories, and `public-reader` + for public repositories. + default: '' + repox-url: + description: URL for Repox used to resolve dependencies. Defaults to SaaS on GitHub-hosted runners and the production Edge + on self-hosted runners. Explicit URLs override runner selection. + default: ${{ runner.environment == 'github-hosted' && 'https://repox.jfrog.io' || 'https://repox-internal.sonar.build' }} + disable-caching: + description: Whether to disable Bun install caching entirely + default: 'false' + host-actions-root: + description: Path to the actions folder on the host (used when called from another local action) + default: '' + +outputs: + registry: + description: The npm registry URL selected for Bun. Also set as environment variable BUN_CONFIG_REGISTRY. + value: ${{ steps.config.outputs.registry }} + +runs: + using: composite + steps: + - id: config-bun-completed + if: env.CONFIG_BUN_COMPLETED != '' + shell: bash + run: | + echo "Action already called by $CONFIG_BUN_COMPLETED, execution skipped." + echo "skip=true" >> "$GITHUB_OUTPUT" + + - name: Set local action paths + if: steps.config-bun-completed.outputs.skip != 'true' + shell: bash + env: + ARTIFACTORY_READER_ROLE: ${{ inputs.artifactory-reader-role != '' && inputs.artifactory-reader-role || + (github.event.repository.visibility == 'public' && 'public-reader' || 'private-reader') }} + run: | + ACTION_PATH_CONFIG_BUN="${{ github.action_path }}" + host_actions_root="${{ inputs.host-actions-root }}" + if [[ -n "$host_actions_root" ]]; then + ACTION_PATH_CONFIG_BUN="$host_actions_root/config-bun" + fi + echo "ACTION_PATH_CONFIG_BUN=$ACTION_PATH_CONFIG_BUN" >> "$GITHUB_ENV" + echo "ARTIFACTORY_READER_ROLE=${ARTIFACTORY_READER_ROLE}" >> "$GITHUB_ENV" + + - uses: SonarSource/vault-action-wrapper@a7fa5fd4a4e4e0f2593f6137c0812cac196cf4ce # 3.7.0 + if: steps.config-bun-completed.outputs.skip != 'true' + id: secrets + with: + # yamllint disable rule:line-length + url: ${{ contains(inputs.repox-url, '.dev.sonar.build') && 'https://vault.dev.sonar.build' || 'https://vault.sonar.build' }} + secrets: | + development/${{ contains(inputs.repox-url, 'jfrog.io') && 'artifactory' || (contains(inputs.repox-url, '.dev.sonar.build') && 'artifactory-edge-dev' || 'artifactory-edge-prod') }}/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} username | ARTIFACTORY_USERNAME; + development/${{ contains(inputs.repox-url, 'jfrog.io') && 'artifactory' || (contains(inputs.repox-url, '.dev.sonar.build') && 'artifactory-edge-dev' || 'artifactory-edge-prod') }}/token/{REPO_OWNER_NAME_DASH}-${{ env.ARTIFACTORY_READER_ROLE }} access_token | ARTIFACTORY_ACCESS_TOKEN; + # yamllint enable rule:line-length + + - name: Configure Bun registry + id: config + if: steps.config-bun-completed.outputs.skip != 'true' + shell: bash + working-directory: ${{ inputs.working-directory }} + env: + ARTIFACTORY_URL: ${{ format('{0}/artifactory', inputs.repox-url) }} + ARTIFACTORY_USERNAME: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_USERNAME || '' }} + ARTIFACTORY_ACCESS_TOKEN: ${{ steps.secrets.outputs.vault && fromJSON(steps.secrets.outputs.vault).ARTIFACTORY_ACCESS_TOKEN || '' }} + run: $ACTION_PATH_CONFIG_BUN/bun_config.sh + + - name: Cache Bun dependencies + uses: SonarSource/gh-action_cache@0b86c7a0e6c3cb8238c826be7dd9fdfd02f16f8b # v1.7.3 + if: steps.config-bun-completed.outputs.skip != 'true' && inputs.disable-caching != 'true' + with: + path: ~/.bun/install/cache + key: bun-${{ runner.os }}-${{ hashFiles(format('{0}/bun.lock', inputs.working-directory)) }} + restore-keys: bun-${{ runner.os }}- + + - name: Set Config Bun completed + if: steps.config-bun-completed.outputs.skip != 'true' + shell: bash + run: echo "CONFIG_BUN_COMPLETED=$GITHUB_ACTION" >> "$GITHUB_ENV" diff --git a/config-bun/bun_config.sh b/config-bun/bun_config.sh new file mode 100755 index 00000000..37a13bb9 --- /dev/null +++ b/config-bun/bun_config.sh @@ -0,0 +1,40 @@ +#!/bin/bash +# Config script for Bun to resolve npm packages from SonarSource Artifactory. +# +# Required environment variables (must be explicitly provided): +# - ARTIFACTORY_URL: URL to Artifactory repository +# - ARTIFACTORY_USERNAME: Username for Artifactory authentication +# - ARTIFACTORY_ACCESS_TOKEN: Access token to read Repox repositories +# +# GitHub Actions auto-provided: +# - GITHUB_ENV: Path to GitHub Actions environment file +# - GITHUB_OUTPUT: Path to GitHub Actions output file + +set -euo pipefail + +: "${ARTIFACTORY_URL:?}" "${ARTIFACTORY_USERNAME:?}" "${ARTIFACTORY_ACCESS_TOKEN:?}" "${GITHUB_ENV:?}" "${GITHUB_OUTPUT:?}" + +configure_bun_registry() { + local registry="${ARTIFACTORY_URL}/api/npm/npm/" + + { + echo "BUN_CONFIG_REGISTRY=$registry" + echo "BUN_CONFIG_TOKEN=$ARTIFACTORY_ACCESS_TOKEN" + echo "ARTIFACTORY_USERNAME=$ARTIFACTORY_USERNAME" + echo "ARTIFACTORY_ACCESS_TOKEN=$ARTIFACTORY_ACCESS_TOKEN" + } >> "$GITHUB_ENV" + echo "registry=$registry" >> "$GITHUB_OUTPUT" + echo "Dependency registry: $registry" + return 0 +} + +main() { + echo "::group::Configure Bun" + configure_bun_registry + echo "::endgroup::" + return 0 +} + +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main +fi diff --git a/sonar-project.properties b/sonar-project.properties index 1078f333..05813fdd 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -4,7 +4,7 @@ sonar.projectName=ci-github-actions sonar.sourceEncoding=UTF-8 -sonar.sources=build-poetry,get-build-number,pr_cleanup,promote,build-gradle,build-maven,config-npm,build-npm,build-yarn,shared,config-pip,config-poetry,cache,code-signing,config-gradle,config-maven,check-sca,update-release-channel,report-ci-metrics,config-nuget +sonar.sources=build-poetry,get-build-number,pr_cleanup,promote,build-gradle,build-maven,config-npm,build-npm,build-yarn,shared,config-pip,config-poetry,cache,code-signing,config-gradle,config-maven,check-sca,update-release-channel,report-ci-metrics,config-nuget,config-bun sonar.tests=spec sonar.coverageReportPaths=coverage/coverage_data/sonar_coverage.xml diff --git a/spec/config-bun_spec.sh b/spec/config-bun_spec.sh new file mode 100644 index 00000000..ff41530a --- /dev/null +++ b/spec/config-bun_spec.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +eval "$(shellspec - -c) exit 1" + +export GITHUB_ENV=/dev/null +export GITHUB_OUTPUT=/dev/null +export ARTIFACTORY_URL="https://repox-internal.sonar.build/artifactory" +export ARTIFACTORY_USERNAME="test-user" +export ARTIFACTORY_ACCESS_TOKEN="test-token" + +EDGE_REGISTRY="https://repox-internal.sonar.build/artifactory/api/npm/npm/" + +Describe 'config-bun/bun_config.sh' + It 'does not run main when sourced' + When run source config-bun/bun_config.sh + The status should be success + The lines of output should equal 0 + The lines of error should equal 0 + End +End + +Include config-bun/bun_config.sh + +Describe 'configure_bun_registry()' + It 'exports the selected registry and its reader token' + GITHUB_ENV=$(mktemp) + GITHUB_OUTPUT=$(mktemp) + export GITHUB_ENV GITHUB_OUTPUT + When call configure_bun_registry + The status should be success + The output should equal "Dependency registry: $EDGE_REGISTRY" + The contents of file "$GITHUB_ENV" should include "BUN_CONFIG_REGISTRY=$EDGE_REGISTRY" + The contents of file "$GITHUB_ENV" should include "BUN_CONFIG_TOKEN=test-token" + The contents of file "$GITHUB_ENV" should not include "NPM_CONFIG_REGISTRY" + The contents of file "$GITHUB_ENV" should include "ARTIFACTORY_USERNAME=test-user" + The contents of file "$GITHUB_ENV" should include "ARTIFACTORY_ACCESS_TOKEN=test-token" + The contents of file "$GITHUB_OUTPUT" should equal "registry=$EDGE_REGISTRY" + End +End + +Describe 'main()' + It 'configures the registry within a GitHub Actions group' + GITHUB_ENV=$(mktemp) + GITHUB_OUTPUT=$(mktemp) + export GITHUB_ENV GITHUB_OUTPUT + When run script config-bun/bun_config.sh + The status should be success + The line 1 should equal "::group::Configure Bun" + The line 2 should equal "Dependency registry: $EDGE_REGISTRY" + The line 3 should equal "::endgroup::" + End + + It 'fails without a reader token' + export ARTIFACTORY_ACCESS_TOKEN="" + When run script config-bun/bun_config.sh + The status should be failure + The error should include "ARTIFACTORY_ACCESS_TOKEN" + End +End