Skip to content
Draft
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
93 changes: 93 additions & 0 deletions .github/workflows/consumer-builds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
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: echo "bundlers=$(node tooling/consumer-builds/config.cjs)" >> "$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
83 changes: 73 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,13 @@ 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
run: echo "bundlers=$(node tooling/consumer-builds/config.cjs)" >> "$GITHUB_OUTPUT"

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

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

- name: Deploy Packages
- name: Pack Release Candidates
if: steps.check-changes.outputs.has_changes == 'true'
env:
CHANGED_PACKAGE_NAMES: ${{ needs.analyze-changes.outputs.node-recursive }}
run: |
export CHANGED_PACKAGES="$(node -e 'console.log(JSON.stringify(process.env.CHANGED_PACKAGE_NAMES.trim().split(/\s+/).filter(Boolean)))')"
yarn workspace webex-widgets consumer-builds:pack

- name: Save Release Candidates
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##*/}
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 Tested Packages to NPM
needs: [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
- 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: Publish Verified Archives
if: needs.prepare-release.outputs.has_changes == 'true'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: node tooling/consumer-builds/candidates.cjs publish "$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
92 changes: 92 additions & 0 deletions ai-docs/CONSUMER_BUILDS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Consumer build release checks

## Scope

The checks 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).
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). All five bundlers
are enabled by default. Set a bundler's `enabled` flag to `false` to omit its check from PRs and releases:

```json
"parcel": {"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.

## 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, plus the legacy meetings package if it changed.
A manifest records each package name, version, archive SHA-256, and whether it must be published.
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 verifies the archives again and publishes
the changed packages from those exact archives with the branch dist tag. It does not rebuild them.
The existing version outputs, distributable cache, documentation, tags, and release comments remain available.

The release preparation and consumer jobs have read access and no publishing secrets.
Only the npm publish step receives `NPM_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: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. Do not use a local publish command to test the gate. Unit tests mock publishing.

## Known regression

`next` at `c298215c` contains the wellness inline Webpack loader and development logging output.
The new checks must reject this candidate. [PR #751](https://github.com/webex/widgets/pull/751)
fixes those package outputs and must merge before this release gate can pass for all five bundlers.
This workflow change does not include that package fix.

## 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.
2 changes: 2 additions & 0 deletions ai-docs/SPEC_INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
| Naming a concept | `GLOSSARY.md` |
| Anything touching input/identity/logging | `SECURITY.md` |
| Review & merge | `REVIEW_CHECKLIST.md` |
| Consumer builds / release checks | `CONSUMER_BUILDS.md` |

## Intake Routing
```
Expand Down Expand Up @@ -68,5 +69,6 @@ The intake record confirms scope/modules **against the code** and sets the chang
| Review catalog | `ai-docs/REVIEW_CHECKLIST.md` | 6-core + 4-coverage + 3-cross-cutting review checks |
| SDK reference | `@webex/contact-center` types (`node_modules/@webex/contact-center/dist/types/index.d.ts`) | installed SDK `.d.ts` surface — verify every SDK call |
| Participant Drop intake | `ai-docs/features/participant-drop-intake.md` | Cross-repository SDK/widget contract, behavior, delivery gates, and verification |
| Consumer build release checks | `ai-docs/CONSUMER_BUILDS.md` | Bundler configuration, candidate archives, release gate, and local checks |

_No `DATA_MODEL.md`: this repo owns no persistent datastore (all domain data comes from the SDK at runtime)._
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
"test:e2e": "yarn playwright test",
"test:styles": "yarn workspaces foreach --all --exclude webex-widgets run test:styles",
"test:tooling": "NODE_ENV=test jest --coverage",
"test:consumer-builds": "node tooling/consumer-builds/run.cjs",
"consumer-builds:matrix": "node tooling/consumer-builds/config.cjs",
"consumer-builds:pack": "node tooling/consumer-builds/candidates.cjs pack",
"consumer-builds:publish": "node tooling/consumer-builds/candidates.cjs publish",
"test:cc-widgets": "yarn workspaces foreach --all --exclude webex-widgets --exclude samples-cc-wc-app --exclude samples-cc-react-app run test:unit",
"test:meetings-widget": "yarn workspaces foreach --all --verbose --include @webex/widgets run test:unit",
"build:dev": "NODE_ENV=development yarn build",
Expand Down
Loading
Loading