Skip to content
Merged
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
95 changes: 95 additions & 0 deletions .github/workflows/consumer-builds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: Consumer Bundler Builds

on:
pull_request:
branches: [next]
workflow_call:
inputs:
candidate-artifact:
description: Package archives prepared by the release job
required: true
type: string
bundlers:
description: Enabled bundlers from tooling/consumer-builds/config.json
required: true
type: string

permissions:
contents: read

jobs:
prepare:
name: Prepare PR Consumer Packages
if: github.event_name == 'pull_request'
runs-on: ubuntu-latest
timeout-minutes: 20
outputs:
bundlers: ${{ steps.config.outputs.bundlers }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: yarn
- name: Read Enabled Bundlers
id: config
run: |
BUNDLERS=$(node tooling/consumer-builds/config.cjs)
echo "bundlers=$BUNDLERS" >> "$GITHUB_OUTPUT"
- name: Install and Build Packages
run: |
yarn install --immutable
yarn workspace webex-widgets build:prod
- name: Pack Consumer Candidates
run: yarn workspace webex-widgets consumer-builds:pack
- uses: actions/upload-artifact@v7
with:
name: consumer-candidates-pr
path: .generated/consumer-builds/candidates
include-hidden-files: true
if-no-files-found: error
retention-days: 7

build:
name: Consumer Build (${{ matrix.bundler }})
needs: prepare
if: >-
always() && !cancelled() &&
(needs.prepare.result == 'success' ||
(github.event_name != 'pull_request' && inputs.candidate-artifact != ''))
runs-on: ubuntu-latest
timeout-minutes: 25
strategy:
fail-fast: false
matrix:
bundler: ${{ fromJSON(inputs.bundlers || needs.prepare.outputs.bundlers || '["webpack"]') }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: npm
cache-dependency-path: tooling/consumer-builds/fixture/package-lock.json
- uses: actions/download-artifact@v8
with:
name: ${{ inputs.candidate-artifact || 'consumer-candidates-pr' }}
path: .generated/consumer-builds/candidates
- name: Install Workspace Runner
run: yarn install --immutable
- name: Build and Check Consumer Startup
env:
BUNDLER: ${{ matrix.bundler }}
run: yarn workspace webex-widgets test:consumer-builds "$BUNDLER"
- name: Save Consumer Reports
if: always()
uses: actions/upload-artifact@v7
with:
name: consumer-report-${{ matrix.bundler }}
path: .generated/consumer-builds/reports
include-hidden-files: true
if-no-files-found: warn
retention-days: 7
110 changes: 100 additions & 10 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ on:

env:
rid: ${{ github.run_id }}-${{ github.run_number }}
token: ${{ secrets.NPM_TOKEN }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GIT_AUTHOR_NAME: ${{ github.actor }}
GIT_AUTHOR_EMAIL: ${{ github.actor }}@users.noreply.github.com
HUSKY: 0
Expand Down Expand Up @@ -79,18 +77,24 @@ jobs:
echo "Changed packages (yarn): ${{ steps.generate-package-matrix-yarn-changed.outputs.yarn-changed }}"
echo "Changed packages + dependents (yarn): ${{ steps.generate-package-matrix-yarn-recursive.outputs.yarn-recursive }}"

publish-npm:
name: Publish to NPM
prepare-release:
name: Prepare Release Packages
needs: analyze-changes
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read

outputs:
published: ${{ steps.check-changes.outputs.has_changes }}
has_changes: ${{ steps.check-changes.outputs.has_changes }}
package_versions: ${{ steps.increment.outputs.package_versions }}
bundlers: ${{ steps.config.outputs.bundlers }}

steps:
- name: Checkout Project
uses: actions/checkout@v6
with:
persist-credentials: false

- name: Setup Node
uses: actions/setup-node@v6
Expand Down Expand Up @@ -127,10 +131,15 @@ jobs:
echo "Compared against: ${{ github.event.before }}"
echo "=========================================="

- name: Add NPM token to .yarnrc.yml file
- name: Read Enabled Bundlers
id: config
if: steps.check-changes.outputs.has_changes == 'true'
run: |
echo "npmAuthToken: ${{ env.token }}" >> ~/.yarnrc.yml
BUNDLERS=$(node tooling/consumer-builds/config.cjs)
echo "bundlers=$BUNDLERS" >> "$GITHUB_OUTPUT"

- name: Install Dependencies
run: yarn install --immutable

- name: Sync and Increment Package Versions
id: increment
Expand All @@ -153,11 +162,92 @@ jobs:
path: '**/dist'
key: dist-${{ env.rid }}

- name: Deploy Packages
- name: Pack Release Candidates
if: steps.check-changes.outputs.has_changes == 'true'
run: yarn workspace webex-widgets consumer-builds:pack

- name: Save Prepared Package Versions
if: steps.check-changes.outputs.has_changes == 'true'
run: yarn workspaces foreach -W --from '${{ needs.analyze-changes.outputs.yarn-recursive }}' --verbose run deploy:npm --access public --tag ${GITHUB_REF##*/}
run: |
git diff --binary -- '**/package.json' > .generated/consumer-builds/candidates/release-manifests.patch

- name: Save Release Candidates
if: steps.check-changes.outputs.has_changes == 'true'
uses: actions/upload-artifact@v7
with:
name: consumer-candidates-release
path: .generated/consumer-builds/candidates
include-hidden-files: true
if-no-files-found: error
retention-days: 7

consumer-builds:
name: Check Release Consumers
needs: prepare-release
if: needs.prepare-release.outputs.has_changes == 'true'
permissions:
contents: read
uses: ./.github/workflows/consumer-builds.yml
with:
candidate-artifact: consumer-candidates-release
bundlers: ${{ needs.prepare-release.outputs.bundlers }}

publish-npm:
name: Publish to NPM
needs: [analyze-changes, prepare-release, consumer-builds]
if: >-
always() && !cancelled() && needs.prepare-release.result == 'success' &&
(needs.prepare-release.outputs.has_changes != 'true' || needs.consumer-builds.result == 'success')
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
outputs:
published: ${{ needs.prepare-release.outputs.has_changes }}
package_versions: ${{ needs.prepare-release.outputs.package_versions }}
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
registry-url: https://registry.npmjs.org
cache: yarn
- name: Restore Dependencies Cache
if: needs.prepare-release.outputs.has_changes == 'true'
uses: actions/cache/restore@v5
with:
path: '**/node_modules'
key: node-modules-${{ hashFiles('./yarn.lock') }}
- name: Install Dependencies
if: needs.prepare-release.outputs.has_changes == 'true'
run: yarn install --immutable
- name: Restore Distributables Cache
if: needs.prepare-release.outputs.has_changes == 'true'
uses: actions/cache/restore@v5
with:
path: '**/dist'
key: dist-${{ env.rid }}
fail-on-cache-miss: true
- name: Download Tested Release Candidates
if: needs.prepare-release.outputs.has_changes == 'true'
uses: actions/download-artifact@v8
with:
name: consumer-candidates-release
path: .generated/consumer-builds/candidates
- name: Restore Prepared Package Versions
if: needs.prepare-release.outputs.has_changes == 'true'
run: git apply --allow-empty .generated/consumer-builds/candidates/release-manifests.patch
- name: Verify Restored Workspace Contents
if: needs.prepare-release.outputs.has_changes == 'true'
run: yarn workspace webex-widgets consumer-builds:verify
Comment thread
bhabalan marked this conversation as resolved.
Resolved
- name: Deploy Packages
if: needs.prepare-release.outputs.has_changes == 'true'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
PACKAGES: ${{ needs.analyze-changes.outputs.yarn-recursive }}
YARN_NPM_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: yarn workspaces foreach -W --from "$PACKAGES" --verbose run deploy:npm --access public --tag "$GITHUB_REF_NAME"

publish-documentation:
name: Publish - Documentation
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ reports/
!packages/contact-center/ai-assistant/src/wellness/assets/WellnessBreakAnimationLight.json
!docs/changelog/logs/*.json
!.sdd/manifest.json
!tooling/consumer-builds/config.json
.env*
!.env.default
!package.json
Expand Down
138 changes: 138 additions & 0 deletions ai-docs/CONSUMER_BUILDS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# Consumer build release checks

## Scope

The checks can build a small Contact Center consumer with Webpack, Vite, Parcel, Rollup, and esbuild.
The consumer imports `@webex/cc-widgets` and `@webex/cc-ai-assistant`. A Chromium check loads each
build from a nested URL, checks the React exports, and imports `@webex/cc-widgets/wc` to check
custom element registration. The consumer does not start an SDK session.

The fixture has normal CSS, Sass, font, and image support. It does not have a wellness media loader
or a workaround for Webpack loader syntax. Tool versions and UI peers are fixed in
[`tooling/consumer-builds/fixture/package-lock.json`](../tooling/consumer-builds/fixture/package-lock.json).
The fixture pins `@momentum-ui/react-collaboration` to `26.197.0`, as used by both Contact Center
sample apps. A regression test checks that the fixture and its lockfile match those sample pins.
This version is below the `>=26.201.9` peer range declared by `@webex/cc-station-login`.
The fixture follows the existing Contact Center integration. It does not
confirm compatibility with every declared peer version. See Known regression below.
These checks cover this fixture and these tool versions. They do not confirm all consumer settings,
development servers, media playback, or CSP policies.

## Enable or disable a bundler

Edit [`tooling/consumer-builds/config.json`](../tooling/consumer-builds/config.json). Webpack, Vite,
Parcel, Rollup, and esbuild are enabled by default. Set a bundler's `enabled` flag to `false` to omit
it from PRs and releases:

```json
"rollup": {"enabled": false}
```

Use a boolean value. Keep all five entries. Unknown entries, missing flags, and an empty enabled list
fail configuration validation. At least one bundler must be enabled. The workflow has no separate
list of required bundlers. Disabled tools remain in the fixture lockfile.
Both workflow paths stop on configuration errors before writing a matrix output. A valid opt out
does not require changes to the tooling tests.

## Release sequence

1. `Deploy CD` finds the changed packages, synchronizes and increments versions, and builds production packages.
2. `prepare-release` packs all nine Contact Center packages. A manifest records each package name,
version, and archive SHA-256. The candidate artifact also contains a Git patch of the package
manifests changed by `package-tools`. The existing distributable cache holds the production build.
3. The consumer workflow installs those archives into a separate fixture for each enabled bundler.
It checks archive hashes and package versions, builds the consumer, and checks browser startup.
Each bundler runs in a separate job. A failure does not cancel the other bundler jobs.
4. `publish-npm` runs only after all enabled checks pass. It installs the checkout's dependencies,
restores the production distributable cache, applies the prepared manifest patch, and verifies
the candidate archives again. It repacks the restored workspaces into a separate temporary
directory and compares package names, versions, file paths, executable flags, and SHA-256 file
hashes with the tested archives. Archive order, timestamps, owners, and compression metadata
do not affect this comparison. A content mismatch, missing build cache, or patch conflict stops publishing.
5. The existing `yarn workspaces foreach ... run deploy:npm` command publishes the changed workspaces
with the branch dist tag. Each workspace keeps its own `deploy:npm` script, including the exclusion
for `@webex/test-fixtures`. The publish job does not repeat version selection or rebuild packages.
Yarn packs the restored workspaces during publishing. The existing version outputs, documentation,
tags, and release comments remain available.

The release preparation and consumer jobs have read access and no publishing secrets.
Only the deploy step receives `NPM_TOKEN`, through `YARN_NPM_AUTH_TOKEN`. The reusable workflow does not inherit secrets.
PR checks use the PR merge commit and read access. They do not use `pull_request_target`.
The `@webex/test-fixtures` package is never published.

## Local checks

From the repository root:

```sh
yarn install --immutable
yarn workspace webex-widgets build:prod
yarn workspace webex-widgets consumer-builds:pack
yarn workspace webex-widgets consumer-builds:verify
yarn workspace webex-widgets consumer-builds:matrix
yarn workspace webex-widgets test:consumer-builds webpack
yarn workspace webex-widgets test:consumer-builds vite
yarn workspace webex-widgets test:consumer-builds parcel
yarn workspace webex-widgets test:consumer-builds rollup
yarn workspace webex-widgets test:consumer-builds esbuild
yarn workspace webex-widgets test:tooling --runInBand tooling/tests/consumer-build
```

To check other candidate archives, pass their directory as the second argument to `test:consumer-builds`.
It must contain `manifest.json` and all nine Contact Center archives. The runner creates its installs,
builds, logs, and results under `.generated/consumer-builds/`. GitHub Actions saves build logs and
package hashes as `consumer-report-<bundler>` artifacts, including when a check fails.

Rebuild with `yarn build:dev` before the full unit suite or a commit. The repo unit tests use development
distributables. The release tests use a temporary Git repository to check the manifest patch and a
stub Yarn executable to check `deploy:npm` arguments and failures. They do not publish packages.
Archive comparison tests cover modified build output, added and removed files, changed manifests,
executable flags, package sets and versions, timestamp differences, and temporary directory cleanup.
Archive links and paths outside `package/` are rejected before extraction.

## Known regression

Momentum UI `26.201.9` emits a `::part(icon) path` selector from `MeetingRowContent.style.scss`
that Vite and Parcel reject. The fork run at `fbfb39d1` passed Webpack, Rollup, and esbuild but failed
those two builds. Sampled later versions contain the same Sass block. Version `26.214.0` also
failed both local checks. The earlier fork run at `915df2a` passed all five bundlers with `26.197.0`.

The fixture keeps the Contact Center samples' `26.197.0` pin, and all five bundlers remain required.
Both install commands use `--legacy-peer-deps`, so they do not enforce the station-login peer range.
The higher station-login minimum and the newer Momentum CSS failure remain dependency issues.
This workflow change does not change a published package's peer requirements or patch vendor CSS.
When Contact Center changes its sample pins, update the fixture and lockfile and pass all five
builds and browser checks before release. The fixture regression test detects a pin mismatch.

The original `next` baseline at `c298215c` contained the wellness inline Webpack loader and development
logging output. The consumer checks rejected that candidate. [PR #751](https://github.com/webex/widgets/pull/751)
fixed those package outputs. Its fix is now included through the PR branch's merges from `next`.

## Local validation on 2026-10-13

- Production packages from `next` at `c298215c`: Vite failed on the inline wellness loader request.
Webpack passed. This confirms that the gate catches the reported consumer regression.
- Production archives from PR #751 at `645707ff`: all five builds and Chromium startup checks passed.
Each result records the same nine archive hashes. No package fix was added to this branch.
- Setting `parcel.enabled` to `false` removed Parcel from the matrix. A direct Parcel test command
failed before installation. The default configuration was then restored.
- 51 tooling regression tests passed, with 88.96% line coverage and 84.09% branch coverage across the
configuration, archive, runner, and browser check helpers.
- `actionlint`, formatting checks, and an immutable Yarn install passed. The existing repo styles
check passed with six warnings in the unchanged Web Component sample.
- Publishing all nine Contact Center archives with `--dry-run` passed. No package was published.

Rollback: revert the workflow change. To omit one bundler check, change its config flag instead.

## Release tooling correction on 2026-10-22

The workflow keeps `package-tools sync`, `package-tools increment`, `build:prod`, and workspace
`deploy:npm`. The custom archive publisher and its root package script were removed. Candidate
archives are used for consumer checks. Publishing uses the existing workspace scripts after
restoring the prepared package versions and production distributables.

The 51 targeted tests pass with 88.32% helper line coverage and 84.72% branch coverage. The release
tests cover an empty manifest patch, version restoration, patch conflicts, deploy arguments, and
failed deployment handling. A separate local check with real Yarn 4.5.1 also confirmed restored
versions, package selection, argument forwarding, and the `test-fixtures` exclusion with harmless
workspace deploy scripts. The development build, production build, and styles check passed.
Loading
Loading