Skip to content

Publishing to GitHub Packages #3881

Publishing to GitHub Packages

Publishing to GitHub Packages #3881

name: Publishing to GitHub Packages
on:
workflow_dispatch:
inputs:
tag:
description: "Choose a tag: 'daily' when publishing a daily build with a timestamp version, 'stable' when publishing a stable build from a release branch"
type: choice
options:
- daily
- stable
default: 'daily'
required: false
filter:
type: string
description: Package file filter pattern
required: false
env:
NX_SKIP_NX_CACHE: true
FILTER: ${{ github.event_name == 'workflow_dispatch' && inputs.filter || '' }}
SET_TIMESTAMP_VERSION: ${{ inputs.tag == 'daily' }}
SBOM_PACKAGE_NAMES: devextreme,devextreme-angular,devextreme-react,devextreme-vue,devextreme-themebuilder
jobs:
build:
name: Build packages
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.filter.outputs.packages }}
steps:
- name: Get sources
uses: actions/checkout@v6
- name: Set up nodejs
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
- uses: pnpm/action-setup@v6
with:
run_install: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Set timestamp version
if: ${{ env.SET_TIMESTAMP_VERSION == 'true' }}
run: pnpm run all:set-timestamp-version
- name: Build npm packages
run: pnpm run all:build:internal
# Builds the dx-make-sbom package argument list from known package names and the tgz files produced in artifacts/npm.
# Produces SBOM_PACKAGES for the SBOM build step.
- name: Prepare SBOM package inputs
run: |
package_version=$(node -p "require('./package.json').version")
IFS=',' read -ra package_names <<< "$SBOM_PACKAGE_NAMES"
sbom_packages=()
for package_name in "${package_names[@]}"; do
tgz_path="artifacts/npm/$package_name-$package_version.tgz"
if [ ! -f "$tgz_path" ]; then
echo "Expected package tarball not found: $tgz_path"
exit 1
fi
sbom_packages+=("$package_name(../../$tgz_path)")
done
sbom_packages_value=$(IFS=,; echo "${sbom_packages[*]}")
echo "SBOM_PACKAGES=$sbom_packages_value" >> "$GITHUB_ENV"
echo "$sbom_packages_value"
- name: Set GitHub Packages auth
run: pnpm set //npm.pkg.github.com/:_authToken='${NODE_AUTH_TOKEN}'
# Generates CycloneDX SBOM JSON files for the selected packages using the just-built tgz files.
# Produces packages/sbom/dist/*.cdx.json.
- name: Build SBOMs
working-directory: packages/sbom
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pnpm install --frozen-lockfile
rm -rf dist/
pnpm dx-make-sbom ../../ dist/ "$SBOM_PACKAGES"
cp dist/devextreme.cdx.json dist/devextreme-dist.cdx.json
# Collects concrete SBOM file paths for validation because the shared action expects explicit file names.
# Produces the sbomFiles.outputs.files multiline output.
- name: Collect SBOM files
id: sbomFiles
run: |
shopt -s nullglob
sbom_files=(packages/sbom/dist/*.cdx.json)
if [ ${#sbom_files[@]} -eq 0 ]; then
echo "No SBOM files found in packages/sbom/dist"
exit 1
fi
{
echo "files<<EOF"
printf '%s\n' "${sbom_files[@]}"
echo "EOF"
} >> "$GITHUB_OUTPUT"
# Validates every generated CycloneDX SBOM file with the shared validation action.
# Produces no artifact; fails the workflow if any SBOM is invalid.
- name: Validate SBOMs
uses: DevExpress/github-actions/validate-sbom@5034a6d5e0fd18fc2826ed20a5140f9c83b8994f
with:
input-format: json
input-files: ${{ steps.sbomFiles.outputs.files }}
- name: Build artifacts package
run: pnpm run make-artifacts-package
# Saves generated SBOM files for the publish job.
# Produces the sbom-packages workflow artifact.
- name: Upload SBOMs
uses: actions/upload-artifact@v7
with:
name: sbom-packages
path: packages/sbom/dist
if-no-files-found: error
retention-days: 1
- name: Upload packages
uses: actions/upload-artifact@v7
with:
name: npm-packages
path: artifacts/npm/*.tgz
if-no-files-found: error
retention-days: 1
- name: Filter packages
id: filter
working-directory: artifacts/npm
run: ls *.tgz | grep -E -i "$FILTER" | sed -r 's/^(.*).tgz$/"\1"/g' | paste -sd "," - | sed -r 's/(.*)/packages=[\1]/' >> "$GITHUB_OUTPUT"
publish:
name: Publish package
runs-on: ubuntu-slim
needs: build
strategy:
fail-fast: false
matrix:
package: ${{ fromJSON(needs.build.outputs.packages) }}
steps:
- name: Get sources
uses: actions/checkout@v6
- name: Download packages
uses: actions/download-artifact@v8
with:
name: npm-packages
path: npm-packages
# Restores generated SBOM files from the build job.
# Produces the local sbom-packages directory for matrix publishing.
- name: Download SBOMs
uses: actions/download-artifact@v8
with:
name: sbom-packages
path: sbom-packages
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version-file: '.node-version'
- uses: pnpm/action-setup@v6
with:
run_install: false
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Change package scope
id: scopedPackage
env:
PACKAGE: ${{ matrix.package }}
run: |
SCOPE=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]');
PACKAGE_DIR=$(pnpm --silent run change-package-scope --tgz npm-packages/$PACKAGE.tgz --scope $SCOPE)
echo "packageDir=$PACKAGE_DIR" >> "$GITHUB_OUTPUT";
cd $PACKAGE_DIR;
pnpm pkg get name | tr -d '"' | sed -r 's/(.*)/name=\1/' >> "$GITHUB_OUTPUT";
pnpm pkg get version | tr -d '"' | sed -r 's/(.*)/version=\1/' >> "$GITHUB_OUTPUT";
pnpm pkg get version | tr -d '"' | sed -r 's/([0-9]+\.[0-9]+).*/majorVersion=\1/' >> "$GITHUB_OUTPUT";
# Wraps the matching SBOM JSON file into a minimal @<owner>/<package>-sbom npm package when the matrix package has an SBOM.
# Produces sbomPackage outputs used by the publish step.
- name: Build SBOM package
id: sbomPackage
env:
PACKAGE_NAME: ${{ steps.scopedPackage.outputs.name }}
PACKAGE_VERSION: ${{ steps.scopedPackage.outputs.version }}
run: |
UNSCOPED_PACKAGE_NAME=$(echo "$PACKAGE_NAME" | sed -r 's#^@[^/]+/##');
SBOM_FILE="sbom-packages/$UNSCOPED_PACKAGE_NAME.cdx.json";
if [[ ",$SBOM_PACKAGE_NAMES," != *",$UNSCOPED_PACKAGE_NAME,"* ]]; then
echo "SBOM publishing is not configured for $UNSCOPED_PACKAGE_NAME"
echo "hasSbom=false" >> "$GITHUB_OUTPUT";
exit 0;
fi
if [ ! -f "$SBOM_FILE" ]; then
echo "No SBOM found for $UNSCOPED_PACKAGE_NAME"
echo "hasSbom=false" >> "$GITHUB_OUTPUT";
exit 0;
fi
OWNER=$(echo "${{ github.repository_owner }}" | tr '[:upper:]' '[:lower:]');
SBOM_PACKAGE_NAME="@$OWNER/$UNSCOPED_PACKAGE_NAME-sbom";
SBOM_PACKAGE_DIR="sbom-package/$SBOM_PACKAGE_NAME";
mkdir -p "$SBOM_PACKAGE_DIR";
cp "$SBOM_FILE" "$SBOM_PACKAGE_DIR/";
cd "$SBOM_PACKAGE_DIR";
node -e "const fs = require('fs'); const [name, version] = process.argv.slice(1); fs.writeFileSync('package.json', JSON.stringify({ name, version }, null, 2));" "$SBOM_PACKAGE_NAME" "$PACKAGE_VERSION";
echo "hasSbom=true" >> "$GITHUB_OUTPUT";
echo "packageDir=$PWD" >> "$GITHUB_OUTPUT";
pnpm pkg get name | tr -d '"' | sed -r 's/(.*)/name=\1/' >> "$GITHUB_OUTPUT";
pnpm pkg get version | tr -d '"' | sed -r 's/(.*)/version=\1/' >> "$GITHUB_OUTPUT";
pnpm pkg get version | tr -d '"' | sed -r 's/([0-9]+\.[0-9]+).*/majorVersion=\1/' >> "$GITHUB_OUTPUT";
- name: Set GitHub Packages auth
run: pnpm set //npm.pkg.github.com/:_authToken='${NODE_AUTH_TOKEN}'
# --ignore-scripts is required for publishing devextreme-angular which fails with error:
# 'Trying to publish a package that has been compiled by Ivy in full compilation mode.'
# Should be removed.
- name: Publish to GitHub Packages
working-directory: ${{ steps.scopedPackage.outputs.packageDir }}
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PACKAGE_NAME: ${{ steps.scopedPackage.outputs.name }}
PACKAGE_VERSION: ${{ steps.scopedPackage.outputs.version }}
PACKAGE_VERSION_MAJOR: ${{ steps.scopedPackage.outputs.majorVersion }}
run: |
pnpm publish --no-git-checks --quiet --ignore-scripts --tag $PACKAGE_VERSION_MAJOR-${{ inputs.tag }} --registry https://npm.pkg.github.com;
pnpm dist-tag add $PACKAGE_NAME@$PACKAGE_VERSION latest --registry=https://npm.pkg.github.com;
# Publishes the generated @<owner>/<package>-sbom npm package to GitHub Packages.
# Produces @<owner>/<package>-sbom in the npm.pkg.github.com feed.
- name: Publish SBOM to GitHub Packages
if: ${{ steps.sbomPackage.outputs.hasSbom == 'true' }}
working-directory: ${{ steps.sbomPackage.outputs.packageDir }}
env:
NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PACKAGE_NAME: ${{ steps.sbomPackage.outputs.name }}
PACKAGE_VERSION: ${{ steps.sbomPackage.outputs.version }}
PACKAGE_VERSION_MAJOR: ${{ steps.sbomPackage.outputs.majorVersion }}
run: |
pnpm publish --no-git-checks --quiet --ignore-scripts --tag $PACKAGE_VERSION_MAJOR-${{ inputs.tag }} --registry https://npm.pkg.github.com;
pnpm dist-tag add $PACKAGE_NAME@$PACKAGE_VERSION latest --registry=https://npm.pkg.github.com;
notify:
runs-on: ubuntu-latest
name: Send notifications
needs: [ build, publish ]
if: failure()
steps:
- uses: actions/checkout@v6
- uses: DevExpress/github-actions/send-teams-notification@v1
with:
hook_url: ${{secrets.TEAMS_ALERT}}
bearer_token: ${{secrets.GITHUB_TOKEN}}
specific_repo: DevExpress/DevExtreme