Skip to content
Closed
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
322 changes: 308 additions & 14 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,36 @@ on:
description: "Release tag to register with the MCP Registry (e.g. 15.8.0)"
required: true

permissions:
id-token: write
contents: read
permissions: {}

jobs:
# Builds, packs and inventories the release. Holds no publishing rights and no OIDC token.
build:
name: Build and inventory
if: ${{ github.event_name == 'release' }}
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
env:
VERSION: ${{ github.event.release.tag_name }}
outputs:
npm-tag: ${{ steps.npm-tag.outputs.npm-tag }}
packages: ${{ steps.digests.outputs.packages }}
checksums: ${{ steps.digests.outputs.checksums }}

steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
node-version: 24
registry-url: "https://registry.npmjs.org"
persist-credentials: false

- run: echo "VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV
- run: echo ${VERSION}
# No registry-url: this job never publishes, and the .npmrc it writes references
# ${NODE_AUTH_TOKEN}, which setup-node v7 no longer defaults and yarn 1 refuses to leave unset.
- name: Setup Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24

- name: Install packages
run: yarn install --frozen-lockfile
Expand All @@ -41,27 +53,309 @@ jobs:
run: npm run validate:package --workspace=@igniteui/mcp-server -- --expected-version "$VERSION"

- name: Define npm tag
id: npm-tag
run: |
if [[ ${VERSION} == *"alpha"* || ${VERSION} == *"beta"* || ${VERSION} == *"rc"* ]]; then
NPM_TAG=next
else
NPM_TAG=latest
fi
echo "NPM_TAG=$NPM_TAG" >> $GITHUB_ENV
echo "npm-tag=$NPM_TAG" >> $GITHUB_OUTPUT
echo "$NPM_TAG"

# Packs every publishable package, then describes the exact tarballs it produced. Everything
# downstream uses those files, so their digests, SBOMs, attestations and publication agree.
- name: Generate delivered SBOMs (CycloneDX 1.6)
run: node scripts/build-sbom.js --release="$VERSION" --out=artifacts

- name: Generate build environment SBOM (CycloneDX 1.6)
# Supplementary best-effort inventory: npm tree errors are captured for the release notes.
run: node scripts/build-sbom.js --release="$VERSION" --out=artifacts --build-env

- name: Record package digests
id: digests
run: |
set -euo pipefail
cd artifacts

{
echo "checksums<<CHECKSUMS_EOF"
sha256sum *.tgz
echo "CHECKSUMS_EOF"
echo "packages=$(jq -c . packages.json)"
} >> $GITHUB_OUTPUT

{ echo "### Packed artifacts"; echo '```'; sha256sum *.tgz; echo '```'; } >> $GITHUB_STEP_SUMMARY

# Rendered here because this job has the compiled tooling; the write-scoped job only applies it.
- name: Prepare release notes
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
mkdir -p release-notes
gh release view "$VERSION" -R "${{ github.repository }}" --json body --jq .body > release-notes/original.md
node scripts/update-release-notes.js \
release-notes/original.md \
"artifacts/igniteui-cli-release-${VERSION}.supply-chain.md" \
release-notes/updated.md

- name: Upload release artifacts
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: release-artifacts
path: artifacts/*
retention-days: 30
if-no-files-found: error

- name: Upload release notes
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: release-notes
path: release-notes/*
retention-days: 30
if-no-files-found: error

attest:
name: Attest build provenance
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
attestations: write
env:
# Artifact attestations are unavailable in private repositories outside GitHub Enterprise Cloud.
ATTESTATIONS_ENABLED: ${{ !github.event.repository.private }}

steps:
- name: Download release artifacts
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: release-artifacts
path: artifacts
digest-mismatch: error

- name: Verify the packages are the ones that were built
env:
EXPECTED: ${{ needs.build.outputs.checksums }}
run: |
set -euo pipefail
cd artifacts
printf '%s\n' "$EXPECTED" | sha256sum --check --strict

- name: Attest build provenance
id: attest-provenance
if: env.ATTESTATIONS_ENABLED == 'true'
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: artifacts/*.tgz

- name: Collect attestation bundle
if: env.ATTESTATIONS_ENABLED == 'true'
run: |
set -euo pipefail
mkdir -p attestations
cp "${{ steps.attest-provenance.outputs.bundle-path }}" attestations/provenance.sigstore.json
echo "- Provenance: ${{ steps.attest-provenance.outputs.attestation-url }}" >> $GITHUB_STEP_SUMMARY

- name: Upload attestation bundle
if: env.ATTESTATIONS_ENABLED == 'true'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: attestations-provenance
path: attestations/*
retention-days: 30
if-no-files-found: error

# One SBOM attestation per package: each binds that package's delivered SBOM to its tarball digest.
attest-sbom:
name: Attest SBOM (${{ matrix.package.name }})
needs: build
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
attestations: write
strategy:
matrix:
package: ${{ fromJSON(needs.build.outputs.packages) }}
env:
ATTESTATIONS_ENABLED: ${{ !github.event.repository.private }}

steps:
- name: Download release artifacts
if: env.ATTESTATIONS_ENABLED == 'true'
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: release-artifacts
path: artifacts
digest-mismatch: error

- name: Verify the packages are the ones that were built
if: env.ATTESTATIONS_ENABLED == 'true'
env:
EXPECTED: ${{ needs.build.outputs.checksums }}
run: |
set -euo pipefail
cd artifacts
printf '%s\n' "$EXPECTED" | sha256sum --check --strict

- name: Attest SBOM (CycloneDX)
id: attest-sbom
if: env.ATTESTATIONS_ENABLED == 'true'
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-path: artifacts/${{ matrix.package.tarball }}
sbom-path: artifacts/${{ matrix.package.sbom }}

- name: Collect attestation bundle
if: env.ATTESTATIONS_ENABLED == 'true'
env:
PACKAGE_ID: ${{ matrix.package.id }}
PACKAGE_NAME: ${{ matrix.package.name }}
run: |
set -euo pipefail
mkdir -p attestations
cp "${{ steps.attest-sbom.outputs.bundle-path }}" "attestations/${PACKAGE_ID}.sbom-cyclonedx.sigstore.json"
echo "- SBOM (CycloneDX) for ${PACKAGE_NAME}: ${{ steps.attest-sbom.outputs.attestation-url }}" >> $GITHUB_STEP_SUMMARY

- name: Upload attestation bundle
if: env.ATTESTATIONS_ENABLED == 'true'
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: attestations-sbom-${{ matrix.package.id }}
path: attestations/*
retention-days: 30
if-no-files-found: error

# The only job that can publish to npm. It compiles nothing and never checks out the repository.
publish:
name: Publish to npmjs.org
needs: [build, attest, attest-sbom]
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
id-token: write

steps:
- name: Setup Node.js
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
registry-url: "https://registry.npmjs.org"

- name: Download release artifacts
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: release-artifacts
path: artifacts
digest-mismatch: error

- name: Verify the packages that were built and attested
env:
EXPECTED: ${{ needs.build.outputs.checksums }}
run: |
set -euo pipefail
cd artifacts
printf '%s\n' "$EXPECTED" | sha256sum --check --strict

# Publishing the packed tarballs guarantees the published bytes are the attested bytes.
# Authentication and npm provenance both come from the OIDC token, so no npm token is needed.
- name: Publish packages
# use npm run as yarn run changes the registry and publishes to https://registry.yarnpkg.com
run: npm run deploy ${NPM_TAG}
env:
NPM_TAG: ${{ needs.build.outputs.npm-tag }}
PACKAGES: ${{ needs.build.outputs.packages }}
run: |
set -euo pipefail
cd artifacts
# In dependency order, so no package is published before the workspace packages it depends on.
jq -r '.[].tarball' <<< "$PACKAGES" | while read -r tarball; do
npm publish "./${tarball}" --tag "$NPM_TAG"
done

attach-to-release:
name: Attach release evidence
needs: [build, attest, attest-sbom]
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
env:
VERSION: ${{ github.event.release.tag_name }}

steps:
- name: Download release artifacts
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: release-artifacts
path: artifacts

- name: Download attestation bundles
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
continue-on-error: true
with:
pattern: attestations-*
merge-multiple: true
path: attestations

- name: Download release notes
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: release-notes
path: release-notes

- name: Publish supply-chain notes to the release
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail

# The notes were merged into the body the build job saw; never overwrite a later edit.
gh release view "$VERSION" -R "${{ github.repository }}" --json body --jq .body > current.md
if cmp -s current.md release-notes/original.md; then
gh release edit "$VERSION" -R "${{ github.repository }}" --notes-file release-notes/updated.md
else
echo "::warning title=Release notes::The release notes changed during the build; the supply-chain section was not added. It is in the SBOM archive."
fi

- name: Package the SBOM archive
run: |
set -euo pipefail

zip -j "igniteui-cli-release-${VERSION}.sbom.zip" \
artifacts/*.cdx.json \
"artifacts/igniteui-cli-release-${VERSION}.supply-chain.md" \
"artifacts/igniteui-cli-release-${VERSION}.sbom.README.md"

- name: Attach evidence to the release
env:
GH_TOKEN: ${{ github.token }}
PACKAGES: ${{ needs.build.outputs.packages }}
run: |
set -euo pipefail

gh release upload "$VERSION" --clobber -R "${{ github.repository }}" \
artifacts/*.tgz \
artifacts/*.tgz.sha256 \
artifacts/*.tgz.sha512 \
$(jq -r '.[].sbom | "artifacts/" + .' <<< "$PACKAGES") \
"igniteui-cli-release-${VERSION}.sbom.zip" \
$(ls attestations/*.json 2>/dev/null || true)

# Separate job so a failed registry publish can be retried with "Re-run failed jobs"
# without repeating the npm publish.
# Skipped for prereleases — the MCP registry has no dist-tag equivalent,
# so publishing an alpha/beta/rc would make it the current version.
mcp-registry:
needs: build
if: ${{ !cancelled() && (github.event_name == 'workflow_dispatch' || (needs.build.result == 'success' && !github.event.release.prerelease)) }}
needs: publish
if: ${{ !cancelled() && (github.event_name == 'workflow_dispatch' || (needs.publish.result == 'success' && !github.event.release.prerelease)) }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
defaults:
run:
working-directory: packages/igniteui-mcp/igniteui-doc-mcp
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
},
"scripts": {
"release": "lerna version --conventional-commits --force-publish=* --yes --no-git-tag-version --no-push",
"deploy": "lerna exec 'npm publish --tag'",
"version": "node ./scripts/versionScript.js",
"build": "node ./node_modules/typescript/lib/tsc.js && npm run config-schema && npm run build:mcp",
"build:mcp": "cd packages/igniteui-mcp/igniteui-doc-mcp && npx tsc && npx tsx scripts/build.ts",
Expand Down Expand Up @@ -65,6 +64,7 @@
"handlebars": "4.7.10"
},
"devDependencies": {
"@cyclonedx/cyclonedx-npm": "^6.0.1",
"@inquirer/prompts": "^7.9.0",
"@types/jasmine": "^5.1.13",
"@types/minimatch": "^5.1.2",
Expand Down
8 changes: 4 additions & 4 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "igniteui-cli",
"version": "15.8.2",
"version": "15.8.3-alpha.0",
"description": "CLI tool for creating Ignite UI projects",
"keywords": [
"CLI",
Expand Down Expand Up @@ -66,9 +66,9 @@
"all": true
},
"dependencies": {
"@igniteui/angular-templates": "~22.2.1582",
"@igniteui/cli-core": "~15.8.2",
"@igniteui/mcp-server": "~15.8.2",
"@igniteui/angular-templates": "~22.2.1583-alpha.0",
"@igniteui/cli-core": "~15.8.3-alpha.0",
"@igniteui/mcp-server": "~15.8.3-alpha.0",
"@inquirer/prompts": "^7.9.0",
"chalk": "^5.3.0",
"glob": "^11.0.0",
Expand Down
Loading
Loading