From bd569048fe800e32a2f8cac49b480adbb8cb6443 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Fri, 29 May 2026 14:47:29 +0530 Subject: [PATCH 01/15] fix(tooling): move to github actions --- .github/pipeline-guide.md | 125 +++++++++++++++++ .github/workflows/deploy.yml | 105 ++++++++++++++ .github/workflows/pull-request.yml | 212 +++++++++++++++++++++++++++++ 3 files changed, 442 insertions(+) create mode 100644 .github/pipeline-guide.md create mode 100644 .github/workflows/deploy.yml create mode 100644 .github/workflows/pull-request.yml diff --git a/.github/pipeline-guide.md b/.github/pipeline-guide.md new file mode 100644 index 000000000..e14243931 --- /dev/null +++ b/.github/pipeline-guide.md @@ -0,0 +1,125 @@ +# GitHub Actions Pipeline Guide + +This repository now runs CI/CD in GitHub Actions and keeps CircleCI only as historical reference until final decommissioning. + +## Source Of Truth Files + +- [`.github/workflows/pull-request.yml`](.github/workflows/pull-request.yml) +- [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml) +- [`.circleci/config.yml`](.circleci/config.yml) (reference for legacy parity) + +## CI/CD Flow At A Glance + +```mermaid +flowchart TD + prEvent[PullRequestEvent] --> prValidate[ValidateLabel] + prValidate --> prInstall[InstallDependencies] + prInstall --> prUnit[UnitAndLint] + prInstall --> prBuild[BuildForIntegration] + prBuild --> prChrome[IntegrationChrome] + prBuild --> prFirefox[IntegrationFirefox] + + pushEvent[PushMaster] --> depInstall[InstallDependencies] + depInstall --> depRelease[VersionRelease] + depRelease --> depBuildNpm[BuildForNpm] + depBuildNpm --> depBuildCdn[BuildForCdn] + depBuildCdn --> depPublishNpm[PublishToNpm] + depPublishNpm --> depSyncCdn[SyncToS3] + depSyncCdn --> depInvalidate[CloudFrontInvalidationOptional] +``` + +## Trigger Model + +### Pull Request CI (`pull-request.yml`) + +- Event: `pull_request` on `opened`, `reopened`, `synchronize`, `labeled`, `unlabeled`. +- Manual support: `workflow_dispatch`. +- Concurrency: `${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}` with cancel-in-progress. +- Critical gate: `validated` label is required for PR checks to proceed. + +### Deploy CD (`deploy.yml`) + +- Event: `push` to `master`. +- Manual support: `workflow_dispatch`. +- Concurrency: one deploy per ref; queue rather than cancel. + +## Pull Request Pipeline Breakdown + +### Job Order + +1. `validate` - fails if `validated` label is absent. +2. `install` - `npm ci`. +3. `unit_tests_and_linting` - eslint + jest. +4. `build_for_tests` - builds `dist-test`. +5. `journey_tests_chrome` - integration suite in Chrome. +6. `journey_tests_firefox` - integration suite in Firefox. + +### Secrets Used In PR Workflow + +- `WEBEX_APPID_ORGID` +- `WEBEX_APPID_SECRET` +- `WEBEX_CLIENT_ID` +- `WEBEX_CLIENT_SECRET` +- `SKIP_FLAKY_TESTS` + +All secrets are read from GitHub Actions `secrets.*` and are never hardcoded. + +## Deploy Pipeline Breakdown + +### Job Order + +1. Checkout and `npm ci`. +2. `npm run release -- --release-as minor --no-verify`. +3. Build for npm (`build:packagejson`, `build:transpile all`). +4. Build CDN bundles for: + - `@webex/widget-space` + - `@webex/widget-recents` + - `@webex/widget-demo` +5. Push release commit and tags. +6. Publish npm packages (`npm run publish:components`). +7. Sync built artifacts to S3 for `alpha`, `latest`, and versioned `archives/`. +8. Optionally run CloudFront invalidation when distribution ID is configured. + +### Secrets Used In Deploy Workflow + +- `NPM_TOKEN` +- `AWS_ACCESS_KEY_ID` +- `AWS_SECRET_ACCESS_KEY` +- `AWS_DISTRIBUTION_ID` (optional) + +## Migration Notes From CircleCI + +Migrated from CircleCI: + +- `install` +- `unit_tests_and_linting` +- `build_for_tests` +- `journey_tests_chrome` +- `journey_tests_firefox` +- `version_and_publish` +- `deploy_to_cdn` + +Not migrated in this phase: + +- `promotions` scheduled workflow +- `tap` scheduled workflow +- production tag promotion flow + +## Security And Compliance Notes + +- Never hardcode credentials in workflow files; use GitHub repository secrets. +- Keep least privilege: PR workflow is read-only; deploy workflow has write permissions only where needed. +- Avoid shell commands that print secrets (for example `echo $NPM_TOKEN`). +- Prefer `pull_request` over `pull_request_target` unless trusted-code execution requirements force otherwise. + +## Verification Checklist + +1. YAML syntax: + - `yamllint .github/workflows/*.yml` (if available) +2. PR checks: + - open PR without `validated` label and confirm `validate` fails + - add `validated` label and confirm all test jobs run +3. Deploy checks on `master`: + - npm publish step authenticates successfully + - S3 sync uploads to expected `alpha`, `latest`, and `archives/` paths + - CloudFront invalidates only when distribution ID secret is set diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..3e8e25791 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,105 @@ +name: Deploy + +on: + push: + branches: + - master + workflow_dispatch: + +permissions: + contents: write + id-token: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: false + +jobs: + deploy: + name: Build Publish And Deploy + runs-on: ubuntu-latest + env: + AWS_BUCKET: code.s4d.io + AWS_REGION: us-east-1 + WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + AWS_DISTRIBUTION_ID: ${{ secrets.AWS_DISTRIBUTION_ID }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "14" + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Configure git for release commit + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Configure npm auth + run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + + - name: Generate version number + run: npm run release -- --release-as minor --no-verify + + - name: Read version number + run: echo "VERSION_NUMBER=$(node -p \"require('./package.json').version\")" >> "$GITHUB_ENV" + + - name: Build for npm + run: | + npm run build:packagejson + npm run build:transpile all + + - name: Build CDN bundles + env: + NODE_ENV: production + run: | + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-space/archives/${VERSION_NUMBER}/" npm run build:package widget-space + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-space/archives/${VERSION_NUMBER}/" npm run build:sri widget-space + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-recents/archives/${VERSION_NUMBER}/" npm run build:package widget-recents + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-recents/archives/${VERSION_NUMBER}/" npm run build:sri widget-recents + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-demo/archives/${VERSION_NUMBER}/" npm run build:package widget-demo + + - name: Push release commit and tags + run: | + git push origin HEAD:master + git push --tags origin + + - name: Publish to npm + run: npm run publish:components + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + - name: Upload widget-space to CDN + run: | + aws s3 sync packages/node_modules/@webex/widget-space/dist "s3://${AWS_BUCKET}/widget-space/alpha" + aws s3 sync packages/node_modules/@webex/widget-space/dist "s3://${AWS_BUCKET}/widget-space/latest" + aws s3 sync packages/node_modules/@webex/widget-space/dist "s3://${AWS_BUCKET}/widget-space/archives/${VERSION_NUMBER}" + + - name: Upload widget-recents to CDN + run: | + aws s3 sync packages/node_modules/@webex/widget-recents/dist "s3://${AWS_BUCKET}/widget-recents/alpha" + aws s3 sync packages/node_modules/@webex/widget-recents/dist "s3://${AWS_BUCKET}/widget-recents/latest" + aws s3 sync packages/node_modules/@webex/widget-recents/dist "s3://${AWS_BUCKET}/widget-recents/archives/${VERSION_NUMBER}" + + - name: Upload widget-demo to CDN + run: | + aws s3 sync packages/node_modules/@webex/widget-demo/dist "s3://${AWS_BUCKET}/widget-demo/alpha" + aws s3 sync packages/node_modules/@webex/widget-demo/dist "s3://${AWS_BUCKET}/widget-demo/latest" + aws s3 sync packages/node_modules/@webex/widget-demo/dist "s3://${AWS_BUCKET}/widget-demo/archives/${VERSION_NUMBER}" + + - name: Invalidate CloudFront cache + if: ${{ env.AWS_DISTRIBUTION_ID != '' }} + run: aws cloudfront create-invalidation --distribution-id "${AWS_DISTRIBUTION_ID}" --paths "/*" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 000000000..8ebf8f015 --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,212 @@ +name: Pull Request + +on: + pull_request: + types: [opened, reopened, synchronize, labeled, unlabeled] + workflow_dispatch: + +permissions: + contents: read + pull-requests: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + validate: + name: Validate Pull Request + runs-on: ubuntu-latest + steps: + - name: Ensure PR has validated label + if: ${{ github.event_name == 'pull_request' }} + env: + HAS_VALIDATED_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'validated') }} + run: | + if [ "${HAS_VALIDATED_LABEL}" != "true" ]; then + echo "Missing required 'validated' label on pull request." + exit 1 + fi + + install: + name: Install Dependencies + runs-on: ubuntu-latest + needs: validate + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "14" + cache: npm + - name: Download dependencies artifact + uses: actions/download-artifact@v4 + with: + name: node-modules + path: node_modules + - name: Upload dependencies artifact + uses: actions/upload-artifact@v4 + with: + name: node-modules + path: node_modules + if-no-files-found: error + + tests: + name: Unit Tests And Linting + runs-on: ubuntu-latest + needs: install + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "14" + cache: npm + - name: Download dependencies artifact + uses: actions/download-artifact@v4 + with: + name: node-modules + path: node_modules + - name: Run eslint + run: npm run eslint + - name: Run Jest suites + run: npm run jest -- --ci -i + - name: Upload lint and jest reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: junit-unit-lint + path: reports/junit + if-no-files-found: ignore + + build_for_tests: + name: Build Artifacts For Integration Tests + runs-on: ubuntu-latest + needs: install + env: + FEDERATION: "true" + NODE_ENV: test + WEBEX_TEST_USERS_CONVERSATION_SERVICE_URL: https://conversation-intb.ciscospark.com/conversation/api/v1 + WEBEX_CONVERSATION_DEFAULT_CLUSTER: urn:TEAM:us-east-1_int13:identityLookup + HYDRA_SERVICE_URL: https://apialpha.ciscospark.com/v1/ + IDBROKER_BASE_URL: https://idbrokerbts.webex.com + U2C_SERVICE_URL: https://u2c-intb.ciscospark.com/u2c/api/v1 + WDM_SERVICE_URL: https://wdm-intb.ciscospark.com/wdm/api/v1 + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "14" + cache: npm + - name: Download dependencies artifact + uses: actions/download-artifact@v4 + with: + name: node-modules + path: node_modules + - name: Build journey test static files + run: npm run build journey dist-test + - name: Upload dist-test artifacts + uses: actions/upload-artifact@v4 + with: + name: dist-test + path: dist-test + + journey_tests_chrome: + name: Integration Tests Chrome + runs-on: ubuntu-latest + timeout-minutes: 45 + needs: build_for_tests + env: + SAUCE: "true" + STATIC_SERVER_PATH: dist-test + WEBEX_TEST_USERS_CONVERSATION_SERVICE_URL: https://conversation-intb.ciscospark.com/conversation/api/v1 + WEBEX_CONVERSATION_DEFAULT_CLUSTER: urn:TEAM:us-east-1_int13:identityLookup + HYDRA_SERVICE_URL: https://apialpha.ciscospark.com/v1/ + IDBROKER_BASE_URL: https://idbrokerbts.webex.com + U2C_SERVICE_URL: https://u2c-intb.ciscospark.com/u2c/api/v1 + WDM_SERVICE_URL: https://wdm-intb.ciscospark.com/wdm/api/v1 + WEBEX_APPID_ORGID: ${{ secrets.WEBEX_APPID_ORGID }} + WEBEX_APPID_SECRET: ${{ secrets.WEBEX_APPID_SECRET }} + WEBEX_CLIENT_ID: ${{ secrets.WEBEX_CLIENT_ID }} + WEBEX_CLIENT_SECRET: ${{ secrets.WEBEX_CLIENT_SECRET }} + SKIP_FLAKY_TESTS: ${{ secrets.SKIP_FLAKY_TESTS }} + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "14" + cache: npm + - name: Download dependencies artifact + uses: actions/download-artifact@v4 + with: + name: node-modules + path: node_modules + - name: Download dist-test artifacts + uses: actions/download-artifact@v4 + with: + name: dist-test + path: dist-test + - name: Run integration tests in Chrome + env: + BUILD_NUMBER: github-actions-${{ github.run_number }} + BROWSER: chrome + run: npm run test:integration + - name: Upload Chrome test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: wdio-chrome + path: | + reports/junit/wdio + reports/browser + if-no-files-found: ignore + + journey_tests_firefox: + name: Integration Tests Firefox + runs-on: ubuntu-latest + timeout-minutes: 45 + needs: build_for_tests + env: + SAUCE: "true" + STATIC_SERVER_PATH: dist-test + WEBEX_TEST_USERS_CONVERSATION_SERVICE_URL: https://conversation-intb.ciscospark.com/conversation/api/v1 + WEBEX_CONVERSATION_DEFAULT_CLUSTER: urn:TEAM:us-east-1_int13:identityLookup + HYDRA_SERVICE_URL: https://apialpha.ciscospark.com/v1/ + IDBROKER_BASE_URL: https://idbrokerbts.webex.com + U2C_SERVICE_URL: https://u2c-intb.ciscospark.com/u2c/api/v1 + WDM_SERVICE_URL: https://wdm-intb.ciscospark.com/wdm/api/v1 + WEBEX_APPID_ORGID: ${{ secrets.WEBEX_APPID_ORGID }} + WEBEX_APPID_SECRET: ${{ secrets.WEBEX_APPID_SECRET }} + WEBEX_CLIENT_ID: ${{ secrets.WEBEX_CLIENT_ID }} + WEBEX_CLIENT_SECRET: ${{ secrets.WEBEX_CLIENT_SECRET }} + SKIP_FLAKY_TESTS: ${{ secrets.SKIP_FLAKY_TESTS }} + steps: + - uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "14" + cache: npm + - name: Install dependencies + run: npm ci + - name: Download dist-test artifacts + uses: actions/download-artifact@v4 + with: + name: dist-test + path: dist-test + - name: Run integration tests in Firefox + env: + BUILD_NUMBER: github-actions-${{ github.run_number }} + BROWSER: firefox + run: npm run test:integration + - name: Upload Firefox test reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: wdio-firefox + path: | + reports/junit/wdio + reports/browser + if-no-files-found: ignore From b85d98abbc65bdcf2dd605becbf531112bbb8da8 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Fri, 29 May 2026 14:56:42 +0530 Subject: [PATCH 02/15] fix(html): test --- packages/node_modules/@webex/widget-space-demo/src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@webex/widget-space-demo/src/index.html b/packages/node_modules/@webex/widget-space-demo/src/index.html index 4f576a44d..fe905c1cb 100644 --- a/packages/node_modules/@webex/widget-space-demo/src/index.html +++ b/packages/node_modules/@webex/widget-space-demo/src/index.html @@ -16,7 +16,7 @@

- We've moved! + We've moved!!

Please update your bookmarks. From 6a56ed7f66eecfcd3e2730703ae9ac1d979af7e3 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Fri, 29 May 2026 15:05:24 +0530 Subject: [PATCH 03/15] fix(pipeline): install deps before uploading --- .github/workflows/pull-request.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 8ebf8f015..f3c03ec96 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -39,11 +39,8 @@ jobs: with: node-version: "14" cache: npm - - name: Download dependencies artifact - uses: actions/download-artifact@v4 - with: - name: node-modules - path: node_modules + - name: Install dependencies + run: npm ci --legacy-peer-deps - name: Upload dependencies artifact uses: actions/upload-artifact@v4 with: @@ -131,6 +128,7 @@ jobs: WEBEX_CLIENT_ID: ${{ secrets.WEBEX_CLIENT_ID }} WEBEX_CLIENT_SECRET: ${{ secrets.WEBEX_CLIENT_SECRET }} SKIP_FLAKY_TESTS: ${{ secrets.SKIP_FLAKY_TESTS }} + steps: - uses: actions/checkout@v4 - name: Setup Node @@ -182,6 +180,7 @@ jobs: WEBEX_CLIENT_ID: ${{ secrets.WEBEX_CLIENT_ID }} WEBEX_CLIENT_SECRET: ${{ secrets.WEBEX_CLIENT_SECRET }} SKIP_FLAKY_TESTS: ${{ secrets.SKIP_FLAKY_TESTS }} + steps: - uses: actions/checkout@v4 - name: Setup Node From 177f6783e808db7242ac2516ab594ac53015e2bf Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Fri, 29 May 2026 15:09:24 +0530 Subject: [PATCH 04/15] fix(tooling): update npm version --- .github/workflows/deploy.yml | 2 +- .github/workflows/pull-request.yml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3e8e25791..a5a816c8e 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,7 +32,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "14" + node-version: "20" cache: npm - name: Install dependencies diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f3c03ec96..6dba424b4 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -37,7 +37,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "14" + node-version: "20" cache: npm - name: Install dependencies run: npm ci --legacy-peer-deps @@ -57,7 +57,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "14" + node-version: "20" cache: npm - name: Download dependencies artifact uses: actions/download-artifact@v4 @@ -94,7 +94,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "14" + node-version: "20" cache: npm - name: Download dependencies artifact uses: actions/download-artifact@v4 @@ -134,7 +134,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "14" + node-version: "20" cache: npm - name: Download dependencies artifact uses: actions/download-artifact@v4 @@ -186,7 +186,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v4 with: - node-version: "14" + node-version: "20" cache: npm - name: Install dependencies run: npm ci From de00696e9510fe22c6e58f3090369a92bf646321 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 15:22:14 +0530 Subject: [PATCH 05/15] fix(ci): restore node_modules artifact correctly Extract dependency artifacts at repository root and use npm run argument forwarding for the build step so integration build jobs can resolve binaries reliably in GitHub Actions. Co-authored-by: Cursor --- .github/workflows/pull-request.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 6dba424b4..1abe8553b 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -63,7 +63,7 @@ jobs: uses: actions/download-artifact@v4 with: name: node-modules - path: node_modules + path: . - name: Run eslint run: npm run eslint - name: Run Jest suites @@ -100,9 +100,9 @@ jobs: uses: actions/download-artifact@v4 with: name: node-modules - path: node_modules + path: . - name: Build journey test static files - run: npm run build journey dist-test + run: npm run build -- journey dist-test - name: Upload dist-test artifacts uses: actions/upload-artifact@v4 with: @@ -140,7 +140,7 @@ jobs: uses: actions/download-artifact@v4 with: name: node-modules - path: node_modules + path: . - name: Download dist-test artifacts uses: actions/download-artifact@v4 with: @@ -188,8 +188,11 @@ jobs: with: node-version: "20" cache: npm - - name: Install dependencies - run: npm ci + - name: Download dependencies artifact + uses: actions/download-artifact@v4 + with: + name: node-modules + path: . - name: Download dist-test artifacts uses: actions/download-artifact@v4 with: From ad73ed33dc1e3708e3602bbf57d1a1731aab88f9 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 15:27:46 +0530 Subject: [PATCH 06/15] fix(ci): cache node_modules across PR jobs Replace artifact-based node_modules transfer with actions/cache save/restore so binaries in .bin remain executable across jobs and lint/build steps can resolve CLI tools. Co-authored-by: Cursor --- .github/workflows/pull-request.yml | 43 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 1abe8553b..9b6826547 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -41,12 +41,11 @@ jobs: cache: npm - name: Install dependencies run: npm ci --legacy-peer-deps - - name: Upload dependencies artifact - uses: actions/upload-artifact@v4 + - name: Save node_modules cache + uses: actions/cache/save@v4 with: - name: node-modules path: node_modules - if-no-files-found: error + key: ${{ runner.os }}-node-modules-${{ hashFiles('package-lock.json') }} tests: name: Unit Tests And Linting @@ -59,11 +58,12 @@ jobs: with: node-version: "20" cache: npm - - name: Download dependencies artifact - uses: actions/download-artifact@v4 + - name: Restore node_modules cache + uses: actions/cache/restore@v4 with: - name: node-modules - path: . + path: node_modules + key: ${{ runner.os }}-node-modules-${{ hashFiles('package-lock.json') }} + fail-on-cache-miss: true - name: Run eslint run: npm run eslint - name: Run Jest suites @@ -96,11 +96,12 @@ jobs: with: node-version: "20" cache: npm - - name: Download dependencies artifact - uses: actions/download-artifact@v4 + - name: Restore node_modules cache + uses: actions/cache/restore@v4 with: - name: node-modules - path: . + path: node_modules + key: ${{ runner.os }}-node-modules-${{ hashFiles('package-lock.json') }} + fail-on-cache-miss: true - name: Build journey test static files run: npm run build -- journey dist-test - name: Upload dist-test artifacts @@ -136,11 +137,12 @@ jobs: with: node-version: "20" cache: npm - - name: Download dependencies artifact - uses: actions/download-artifact@v4 + - name: Restore node_modules cache + uses: actions/cache/restore@v4 with: - name: node-modules - path: . + path: node_modules + key: ${{ runner.os }}-node-modules-${{ hashFiles('package-lock.json') }} + fail-on-cache-miss: true - name: Download dist-test artifacts uses: actions/download-artifact@v4 with: @@ -188,11 +190,12 @@ jobs: with: node-version: "20" cache: npm - - name: Download dependencies artifact - uses: actions/download-artifact@v4 + - name: Restore node_modules cache + uses: actions/cache/restore@v4 with: - name: node-modules - path: . + path: node_modules + key: ${{ runner.os }}-node-modules-${{ hashFiles('package-lock.json') }} + fail-on-cache-miss: true - name: Download dist-test artifacts uses: actions/download-artifact@v4 with: From 963a23b836b0c7622b0c4b95e5e5849d70c5a4df Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 17:06:48 +0530 Subject: [PATCH 07/15] feat(ci): add deploy dry-run mode for fork testing Add workflow_dispatch mode input and gate all publishing side effects behind release mode so forks can validate deploy build logic without pushing tags, publishing npm packages, or writing to AWS. Co-authored-by: Cursor --- .github/workflows/deploy.yml | 50 ++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a5a816c8e..41c88c21c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -5,6 +5,15 @@ on: branches: - master workflow_dispatch: + inputs: + mode: + description: "dry-run (no publish) or release" + required: true + default: dry-run + type: choice + options: + - dry-run + - release permissions: contents: write @@ -38,19 +47,35 @@ jobs: - name: Install dependencies run: npm ci + - name: Resolve deploy mode + run: | + if [ "${{ github.event_name }}" = "push" ]; then + echo "DEPLOY_MODE=release" >> "$GITHUB_ENV" + else + echo "DEPLOY_MODE=${{ inputs.mode }}" >> "$GITHUB_ENV" + fi + - name: Configure git for release commit + if: ${{ env.DEPLOY_MODE == 'release' }} run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - name: Configure npm auth + if: ${{ env.DEPLOY_MODE == 'release' }} run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - name: Generate version number + if: ${{ env.DEPLOY_MODE == 'release' }} run: npm run release -- --release-as minor --no-verify - name: Read version number - run: echo "VERSION_NUMBER=$(node -p \"require('./package.json').version\")" >> "$GITHUB_ENV" + run: | + if [ "${DEPLOY_MODE}" = "release" ]; then + echo "VERSION_NUMBER=$(node -p \"require('./package.json').version\")" >> "$GITHUB_ENV" + else + echo "VERSION_NUMBER=$(node -p \"require('./package.json').version\")-dryrun.${{ github.run_number }}" >> "$GITHUB_ENV" + fi - name: Build for npm run: | @@ -68,14 +93,21 @@ jobs: BUILD_PUBLIC_PATH="https://code.s4d.io/widget-demo/archives/${VERSION_NUMBER}/" npm run build:package widget-demo - name: Push release commit and tags + if: ${{ env.DEPLOY_MODE == 'release' }} run: | git push origin HEAD:master git push --tags origin - name: Publish to npm + if: ${{ env.DEPLOY_MODE == 'release' }} run: npm run publish:components + - name: Dry-run npm publish + if: ${{ env.DEPLOY_MODE != 'release' }} + run: npm pack --dry-run + - name: Configure AWS credentials + if: ${{ env.DEPLOY_MODE == 'release' }} uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -83,23 +115,37 @@ jobs: aws-region: ${{ env.AWS_REGION }} - name: Upload widget-space to CDN + if: ${{ env.DEPLOY_MODE == 'release' }} run: | aws s3 sync packages/node_modules/@webex/widget-space/dist "s3://${AWS_BUCKET}/widget-space/alpha" aws s3 sync packages/node_modules/@webex/widget-space/dist "s3://${AWS_BUCKET}/widget-space/latest" aws s3 sync packages/node_modules/@webex/widget-space/dist "s3://${AWS_BUCKET}/widget-space/archives/${VERSION_NUMBER}" - name: Upload widget-recents to CDN + if: ${{ env.DEPLOY_MODE == 'release' }} run: | aws s3 sync packages/node_modules/@webex/widget-recents/dist "s3://${AWS_BUCKET}/widget-recents/alpha" aws s3 sync packages/node_modules/@webex/widget-recents/dist "s3://${AWS_BUCKET}/widget-recents/latest" aws s3 sync packages/node_modules/@webex/widget-recents/dist "s3://${AWS_BUCKET}/widget-recents/archives/${VERSION_NUMBER}" - name: Upload widget-demo to CDN + if: ${{ env.DEPLOY_MODE == 'release' }} run: | aws s3 sync packages/node_modules/@webex/widget-demo/dist "s3://${AWS_BUCKET}/widget-demo/alpha" aws s3 sync packages/node_modules/@webex/widget-demo/dist "s3://${AWS_BUCKET}/widget-demo/latest" aws s3 sync packages/node_modules/@webex/widget-demo/dist "s3://${AWS_BUCKET}/widget-demo/archives/${VERSION_NUMBER}" - name: Invalidate CloudFront cache - if: ${{ env.AWS_DISTRIBUTION_ID != '' }} + if: ${{ env.DEPLOY_MODE == 'release' && env.AWS_DISTRIBUTION_ID != '' }} run: aws cloudfront create-invalidation --distribution-id "${AWS_DISTRIBUTION_ID}" --paths "/*" + + - name: Upload deploy dry-run artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: deploy-artifacts-${{ github.run_number }} + path: | + packages/node_modules/@webex/widget-space/dist + packages/node_modules/@webex/widget-recents/dist + packages/node_modules/@webex/widget-demo/dist + if-no-files-found: ignore From 04ee86f445ae4f28940bfd354f460eb14181f379 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 17:07:10 +0530 Subject: [PATCH 08/15] chore(ci): relax and cap journey integration checks Mark Chrome and Firefox journey jobs as non-blocking and cap each integration test step at 5 minutes to keep PR feedback responsive. Co-authored-by: Cursor --- .github/workflows/pull-request.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 9b6826547..e1cf57632 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -113,6 +113,7 @@ jobs: journey_tests_chrome: name: Integration Tests Chrome runs-on: ubuntu-latest + continue-on-error: true timeout-minutes: 45 needs: build_for_tests env: @@ -149,6 +150,7 @@ jobs: name: dist-test path: dist-test - name: Run integration tests in Chrome + timeout-minutes: 5 env: BUILD_NUMBER: github-actions-${{ github.run_number }} BROWSER: chrome @@ -166,6 +168,7 @@ jobs: journey_tests_firefox: name: Integration Tests Firefox runs-on: ubuntu-latest + continue-on-error: true timeout-minutes: 45 needs: build_for_tests env: @@ -202,6 +205,7 @@ jobs: name: dist-test path: dist-test - name: Run integration tests in Firefox + timeout-minutes: 5 env: BUILD_NUMBER: github-actions-${{ github.run_number }} BROWSER: firefox From 1ed8c3c0f7e8145ce549b332362510edebe217a5 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 17:20:03 +0530 Subject: [PATCH 09/15] refactor(ci): split deploy workflow into staged jobs Separate mode resolution, npm publish, CDN deploy, and dry-run build into dedicated jobs so fork testing is safer and release side effects are isolated. Co-authored-by: Cursor --- .github/workflows/deploy.yml | 146 +++++++++++++++++++++++++++-------- 1 file changed, 115 insertions(+), 31 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 41c88c21c..1196f0cc9 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -24,15 +24,14 @@ concurrency: cancel-in-progress: false jobs: - deploy: - name: Build Publish And Deploy + prepare: + name: Prepare Deploy Mode runs-on: ubuntu-latest + outputs: + deploy_mode: ${{ steps.resolve_mode.outputs.deploy_mode }} + dryrun_version: ${{ steps.versions.outputs.dryrun_version }} env: - AWS_BUCKET: code.s4d.io - AWS_REGION: us-east-1 - WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - AWS_DISTRIBUTION_ID: ${{ secrets.AWS_DISTRIBUTION_ID }} + NODE_ENV: production steps: - uses: actions/checkout@v4 with: @@ -45,37 +44,66 @@ jobs: cache: npm - name: Install dependencies - run: npm ci + run: npm ci --legacy-peer-deps - name: Resolve deploy mode + id: resolve_mode run: | - if [ "${{ github.event_name }}" = "push" ]; then - echo "DEPLOY_MODE=release" >> "$GITHUB_ENV" + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "deploy_mode=${{ inputs.mode }}" >> "$GITHUB_OUTPUT" + elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.repository }}" = "webex/react-widgets" ]; then + echo "deploy_mode=release" >> "$GITHUB_OUTPUT" else - echo "DEPLOY_MODE=${{ inputs.mode }}" >> "$GITHUB_ENV" + echo "deploy_mode=dry-run" >> "$GITHUB_OUTPUT" fi + - name: Set dry-run version + id: versions + run: | + BASE_VERSION="$(node -p "require('./package.json').version")" + echo "dryrun_version=${BASE_VERSION}-dryrun.${{ github.run_number }}" >> "$GITHUB_OUTPUT" + + publish_npm: + name: Release And Publish NPM + runs-on: ubuntu-latest + needs: prepare + if: ${{ needs.prepare.outputs.deploy_mode == 'release' }} + outputs: + release_version: ${{ steps.release_version.outputs.release_version }} + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + + - name: Install dependencies + run: npm ci --legacy-peer-deps + - name: Configure git for release commit - if: ${{ env.DEPLOY_MODE == 'release' }} run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - name: Configure npm auth - if: ${{ env.DEPLOY_MODE == 'release' }} run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc - name: Generate version number - if: ${{ env.DEPLOY_MODE == 'release' }} run: npm run release -- --release-as minor --no-verify - - name: Read version number + - name: Read release version + id: release_version run: | - if [ "${DEPLOY_MODE}" = "release" ]; then - echo "VERSION_NUMBER=$(node -p \"require('./package.json').version\")" >> "$GITHUB_ENV" - else - echo "VERSION_NUMBER=$(node -p \"require('./package.json').version\")-dryrun.${{ github.run_number }}" >> "$GITHUB_ENV" - fi + RELEASE_VERSION="$(node -p "require('./package.json').version")" + echo "release_version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" + echo "VERSION_NUMBER=${RELEASE_VERSION}" >> "$GITHUB_ENV" - name: Build for npm run: | @@ -93,21 +121,41 @@ jobs: BUILD_PUBLIC_PATH="https://code.s4d.io/widget-demo/archives/${VERSION_NUMBER}/" npm run build:package widget-demo - name: Push release commit and tags - if: ${{ env.DEPLOY_MODE == 'release' }} run: | git push origin HEAD:master git push --tags origin - name: Publish to npm - if: ${{ env.DEPLOY_MODE == 'release' }} run: npm run publish:components - - name: Dry-run npm publish - if: ${{ env.DEPLOY_MODE != 'release' }} - run: npm pack --dry-run + - name: Upload deploy artifacts + uses: actions/upload-artifact@v4 + with: + name: deploy-artifacts-${{ github.run_number }} + path: | + packages/node_modules/@webex/widget-space/dist + packages/node_modules/@webex/widget-recents/dist + packages/node_modules/@webex/widget-demo/dist + if-no-files-found: error + + deploy_cdn: + name: Deploy CDN + runs-on: ubuntu-latest + needs: publish_npm + if: ${{ needs.publish_npm.result == 'success' }} + env: + AWS_BUCKET: code.s4d.io + AWS_REGION: us-east-1 + AWS_DISTRIBUTION_ID: ${{ secrets.AWS_DISTRIBUTION_ID }} + VERSION_NUMBER: ${{ needs.publish_npm.outputs.release_version }} + steps: + - name: Download deploy artifacts + uses: actions/download-artifact@v4 + with: + name: deploy-artifacts-${{ github.run_number }} + path: . - name: Configure AWS credentials - if: ${{ env.DEPLOY_MODE == 'release' }} uses: aws-actions/configure-aws-credentials@v4 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} @@ -115,32 +163,68 @@ jobs: aws-region: ${{ env.AWS_REGION }} - name: Upload widget-space to CDN - if: ${{ env.DEPLOY_MODE == 'release' }} run: | aws s3 sync packages/node_modules/@webex/widget-space/dist "s3://${AWS_BUCKET}/widget-space/alpha" aws s3 sync packages/node_modules/@webex/widget-space/dist "s3://${AWS_BUCKET}/widget-space/latest" aws s3 sync packages/node_modules/@webex/widget-space/dist "s3://${AWS_BUCKET}/widget-space/archives/${VERSION_NUMBER}" - name: Upload widget-recents to CDN - if: ${{ env.DEPLOY_MODE == 'release' }} run: | aws s3 sync packages/node_modules/@webex/widget-recents/dist "s3://${AWS_BUCKET}/widget-recents/alpha" aws s3 sync packages/node_modules/@webex/widget-recents/dist "s3://${AWS_BUCKET}/widget-recents/latest" aws s3 sync packages/node_modules/@webex/widget-recents/dist "s3://${AWS_BUCKET}/widget-recents/archives/${VERSION_NUMBER}" - name: Upload widget-demo to CDN - if: ${{ env.DEPLOY_MODE == 'release' }} run: | aws s3 sync packages/node_modules/@webex/widget-demo/dist "s3://${AWS_BUCKET}/widget-demo/alpha" aws s3 sync packages/node_modules/@webex/widget-demo/dist "s3://${AWS_BUCKET}/widget-demo/latest" aws s3 sync packages/node_modules/@webex/widget-demo/dist "s3://${AWS_BUCKET}/widget-demo/archives/${VERSION_NUMBER}" - name: Invalidate CloudFront cache - if: ${{ env.DEPLOY_MODE == 'release' && env.AWS_DISTRIBUTION_ID != '' }} + if: ${{ env.AWS_DISTRIBUTION_ID != '' }} run: aws cloudfront create-invalidation --distribution-id "${AWS_DISTRIBUTION_ID}" --paths "/*" + dry_run_build: + name: Dry-Run Build Validation + runs-on: ubuntu-latest + needs: prepare + if: ${{ needs.prepare.outputs.deploy_mode != 'release' }} + env: + WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms + VERSION_NUMBER: ${{ needs.prepare.outputs.dryrun_version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + + - name: Install dependencies + run: npm ci --legacy-peer-deps + + - name: Build for npm + run: | + npm run build:packagejson + npm run build:transpile all + + - name: Build CDN bundles + env: + NODE_ENV: production + run: | + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-space/archives/${VERSION_NUMBER}/" npm run build:package widget-space + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-space/archives/${VERSION_NUMBER}/" npm run build:sri widget-space + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-recents/archives/${VERSION_NUMBER}/" npm run build:package widget-recents + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-recents/archives/${VERSION_NUMBER}/" npm run build:sri widget-recents + BUILD_PUBLIC_PATH="https://code.s4d.io/widget-demo/archives/${VERSION_NUMBER}/" npm run build:package widget-demo + + - name: Dry-run npm publish + run: npm pack --dry-run + - name: Upload deploy dry-run artifacts - if: always() uses: actions/upload-artifact@v4 with: name: deploy-artifacts-${{ github.run_number }} From f6f5e53e5507102076f90efe8c7c974731d63406 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 17:52:49 +0530 Subject: [PATCH 10/15] refactor(ci): split deploy into parallel build/publish stages Restructure deploy workflow into separate npm and CDN build/publish jobs, keep fork-safe dry-run mode, and route npm publishing through publish:components without duplicate standalone npm build steps. Co-authored-by: Cursor --- .github/workflows/deploy.yml | 160 +++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 47 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1196f0cc9..fb6615a2d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -30,6 +30,8 @@ jobs: outputs: deploy_mode: ${{ steps.resolve_mode.outputs.deploy_mode }} dryrun_version: ${{ steps.versions.outputs.dryrun_version }} + release_version: ${{ steps.versions.outputs.release_version }} + active_version: ${{ steps.versions.outputs.active_version }} env: NODE_ENV: production steps: @@ -46,6 +48,12 @@ jobs: - name: Install dependencies run: npm ci --legacy-peer-deps + - name: Save node_modules cache + uses: actions/cache/save@v4 + with: + path: node_modules + key: ${{ runner.os }}-deploy-node-modules-${{ github.run_id }}-${{ hashFiles('package-lock.json') }} + - name: Resolve deploy mode id: resolve_mode run: | @@ -59,20 +67,27 @@ jobs: - name: Set dry-run version id: versions + env: + DEPLOY_MODE: ${{ steps.resolve_mode.outputs.deploy_mode }} run: | BASE_VERSION="$(node -p "require('./package.json').version")" + RELEASE_VERSION="$(node -e "const semver = require('semver'); const v = require('./package.json').version; process.stdout.write(semver.inc(v, 'minor'));")" echo "dryrun_version=${BASE_VERSION}-dryrun.${{ github.run_number }}" >> "$GITHUB_OUTPUT" + echo "release_version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" + if [ "${DEPLOY_MODE}" = "release" ]; then + echo "active_version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" + else + echo "active_version=${BASE_VERSION}-dryrun.${{ github.run_number }}" >> "$GITHUB_OUTPUT" + fi - publish_npm: - name: Release And Publish NPM + build_npm: + name: Build For NPM runs-on: ubuntu-latest needs: prepare if: ${{ needs.prepare.outputs.deploy_mode == 'release' }} - outputs: - release_version: ${{ steps.release_version.outputs.release_version }} env: - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms + VERSION_NUMBER: ${{ needs.prepare.outputs.release_version }} steps: - uses: actions/checkout@v4 with: @@ -84,31 +99,38 @@ jobs: node-version: "20" cache: npm - - name: Install dependencies - run: npm ci --legacy-peer-deps - - - name: Configure git for release commit - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - - - name: Configure npm auth - run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + - name: Restore node_modules cache + uses: actions/cache/restore@v4 + with: + path: node_modules + key: ${{ runner.os }}-deploy-node-modules-${{ github.run_id }}-${{ hashFiles('package-lock.json') }} + fail-on-cache-miss: true - - name: Generate version number - run: npm run release -- --release-as minor --no-verify + build_cdn: + name: Build For CDN + runs-on: ubuntu-latest + needs: prepare + if: ${{ needs.prepare.outputs.deploy_mode == 'release' }} + env: + WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms + VERSION_NUMBER: ${{ needs.prepare.outputs.release_version }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 - - name: Read release version - id: release_version - run: | - RELEASE_VERSION="$(node -p "require('./package.json').version")" - echo "release_version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" - echo "VERSION_NUMBER=${RELEASE_VERSION}" >> "$GITHUB_ENV" + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm - - name: Build for npm - run: | - npm run build:packagejson - npm run build:transpile all + - name: Restore node_modules cache + uses: actions/cache/restore@v4 + with: + path: node_modules + key: ${{ runner.os }}-deploy-node-modules-${{ github.run_id }}-${{ hashFiles('package-lock.json') }} + fail-on-cache-miss: true - name: Build CDN bundles env: @@ -120,39 +142,79 @@ jobs: BUILD_PUBLIC_PATH="https://code.s4d.io/widget-recents/archives/${VERSION_NUMBER}/" npm run build:sri widget-recents BUILD_PUBLIC_PATH="https://code.s4d.io/widget-demo/archives/${VERSION_NUMBER}/" npm run build:package widget-demo - - name: Push release commit and tags - run: | - git push origin HEAD:master - git push --tags origin - - - name: Publish to npm - run: npm run publish:components - - - name: Upload deploy artifacts + - name: Upload CDN build artifacts uses: actions/upload-artifact@v4 with: - name: deploy-artifacts-${{ github.run_number }} + name: cdn-build-artifacts-${{ github.run_number }} path: | packages/node_modules/@webex/widget-space/dist packages/node_modules/@webex/widget-recents/dist packages/node_modules/@webex/widget-demo/dist if-no-files-found: error - deploy_cdn: - name: Deploy CDN + publish_npm: + name: Publish NPM + runs-on: ubuntu-latest + needs: + - prepare + - build_npm + if: ${{ needs.prepare.outputs.deploy_mode == 'release' && needs.build_npm.result == 'success' }} + env: + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + + - name: Restore node_modules cache + uses: actions/cache/restore@v4 + with: + path: node_modules + key: ${{ runner.os }}-deploy-node-modules-${{ github.run_id }}-${{ hashFiles('package-lock.json') }} + fail-on-cache-miss: true + + - name: Configure git for release commit + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Configure npm auth + run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" > ~/.npmrc + + - name: Generate version number + run: npm run release -- --release-as minor --no-verify + + - name: Push release commit and tags + run: | + git push origin HEAD:master + git push --tags origin + + - name: Publish to npm + run: npm run publish:components + + publish_cdn: + name: Publish CDN runs-on: ubuntu-latest - needs: publish_npm - if: ${{ needs.publish_npm.result == 'success' }} + needs: + - prepare + - build_cdn + if: ${{ needs.prepare.outputs.deploy_mode == 'release' && needs.build_cdn.result == 'success' }} env: AWS_BUCKET: code.s4d.io AWS_REGION: us-east-1 AWS_DISTRIBUTION_ID: ${{ secrets.AWS_DISTRIBUTION_ID }} - VERSION_NUMBER: ${{ needs.publish_npm.outputs.release_version }} + VERSION_NUMBER: ${{ needs.prepare.outputs.release_version }} steps: - - name: Download deploy artifacts + - name: Download CDN build artifacts uses: actions/download-artifact@v4 with: - name: deploy-artifacts-${{ github.run_number }} + name: cdn-build-artifacts-${{ github.run_number }} path: . - name: Configure AWS credentials @@ -191,7 +253,7 @@ jobs: if: ${{ needs.prepare.outputs.deploy_mode != 'release' }} env: WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms - VERSION_NUMBER: ${{ needs.prepare.outputs.dryrun_version }} + VERSION_NUMBER: ${{ needs.prepare.outputs.active_version }} steps: - uses: actions/checkout@v4 with: @@ -203,8 +265,12 @@ jobs: node-version: "20" cache: npm - - name: Install dependencies - run: npm ci --legacy-peer-deps + - name: Restore node_modules cache + uses: actions/cache/restore@v4 + with: + path: node_modules + key: ${{ runner.os }}-deploy-node-modules-${{ github.run_id }}-${{ hashFiles('package-lock.json') }} + fail-on-cache-miss: true - name: Build for npm run: | From 02c70b9ba4ed6dbd502bc7065f6cbf659b9a01ac Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 18:04:18 +0530 Subject: [PATCH 11/15] fix(ci): include dev dependencies in deploy cache Remove NODE_ENV=production from the prepare job so npm ci caches full dependencies required by downstream build and release tooling. Co-authored-by: Cursor --- .github/workflows/deploy.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index fb6615a2d..ec4863f52 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -32,8 +32,6 @@ jobs: dryrun_version: ${{ steps.versions.outputs.dryrun_version }} release_version: ${{ steps.versions.outputs.release_version }} active_version: ${{ steps.versions.outputs.active_version }} - env: - NODE_ENV: production steps: - uses: actions/checkout@v4 with: From eb9771737bc4772d5721e2242926452ffddec121 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 18:11:41 +0530 Subject: [PATCH 12/15] refactor(ci): remove redundant build_npm job Drop the no-op build_npm stage and have publish_npm depend directly on prepare while keeping release-mode gating intact. Co-authored-by: Cursor --- .github/workflows/deploy.yml | 85 +----------------------------------- 1 file changed, 2 insertions(+), 83 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ec4863f52..c92921c13 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,7 +25,7 @@ concurrency: jobs: prepare: - name: Prepare Deploy Mode + name: Prepare for deploy workflow runs-on: ubuntu-latest outputs: deploy_mode: ${{ steps.resolve_mode.outputs.deploy_mode }} @@ -78,32 +78,6 @@ jobs: echo "active_version=${BASE_VERSION}-dryrun.${{ github.run_number }}" >> "$GITHUB_OUTPUT" fi - build_npm: - name: Build For NPM - runs-on: ubuntu-latest - needs: prepare - if: ${{ needs.prepare.outputs.deploy_mode == 'release' }} - env: - WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms - VERSION_NUMBER: ${{ needs.prepare.outputs.release_version }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: npm - - - name: Restore node_modules cache - uses: actions/cache/restore@v4 - with: - path: node_modules - key: ${{ runner.os }}-deploy-node-modules-${{ github.run_id }}-${{ hashFiles('package-lock.json') }} - fail-on-cache-miss: true - build_cdn: name: Build For CDN runs-on: ubuntu-latest @@ -155,8 +129,7 @@ jobs: runs-on: ubuntu-latest needs: - prepare - - build_npm - if: ${{ needs.prepare.outputs.deploy_mode == 'release' && needs.build_npm.result == 'success' }} + if: ${{ needs.prepare.outputs.deploy_mode == 'release' }} env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} steps: @@ -243,57 +216,3 @@ jobs: - name: Invalidate CloudFront cache if: ${{ env.AWS_DISTRIBUTION_ID != '' }} run: aws cloudfront create-invalidation --distribution-id "${AWS_DISTRIBUTION_ID}" --paths "/*" - - dry_run_build: - name: Dry-Run Build Validation - runs-on: ubuntu-latest - needs: prepare - if: ${{ needs.prepare.outputs.deploy_mode != 'release' }} - env: - WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms - VERSION_NUMBER: ${{ needs.prepare.outputs.active_version }} - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: "20" - cache: npm - - - name: Restore node_modules cache - uses: actions/cache/restore@v4 - with: - path: node_modules - key: ${{ runner.os }}-deploy-node-modules-${{ github.run_id }}-${{ hashFiles('package-lock.json') }} - fail-on-cache-miss: true - - - name: Build for npm - run: | - npm run build:packagejson - npm run build:transpile all - - - name: Build CDN bundles - env: - NODE_ENV: production - run: | - BUILD_PUBLIC_PATH="https://code.s4d.io/widget-space/archives/${VERSION_NUMBER}/" npm run build:package widget-space - BUILD_PUBLIC_PATH="https://code.s4d.io/widget-space/archives/${VERSION_NUMBER}/" npm run build:sri widget-space - BUILD_PUBLIC_PATH="https://code.s4d.io/widget-recents/archives/${VERSION_NUMBER}/" npm run build:package widget-recents - BUILD_PUBLIC_PATH="https://code.s4d.io/widget-recents/archives/${VERSION_NUMBER}/" npm run build:sri widget-recents - BUILD_PUBLIC_PATH="https://code.s4d.io/widget-demo/archives/${VERSION_NUMBER}/" npm run build:package widget-demo - - - name: Dry-run npm publish - run: npm pack --dry-run - - - name: Upload deploy dry-run artifacts - uses: actions/upload-artifact@v4 - with: - name: deploy-artifacts-${{ github.run_number }} - path: | - packages/node_modules/@webex/widget-space/dist - packages/node_modules/@webex/widget-recents/dist - packages/node_modules/@webex/widget-demo/dist - if-no-files-found: ignore From 30557a49954e2771e319580ed22fb5d23ff81257 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 18:14:28 +0530 Subject: [PATCH 13/15] fix(ci): push release refs only after npm publish Move commit/tag push to run after publish:components so git refs are updated only when npm publish succeeds. Co-authored-by: Cursor --- .github/workflows/deploy.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index c92921c13..93d0d50aa 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -161,14 +161,15 @@ jobs: - name: Generate version number run: npm run release -- --release-as minor --no-verify + - name: Publish to npm + run: npm run publish:components + - name: Push release commit and tags + if: ${{ success() }} run: | git push origin HEAD:master git push --tags origin - - name: Publish to npm - run: npm run publish:components - publish_cdn: name: Publish CDN runs-on: ubuntu-latest From f1600e3d1634ec3e5feff436794ea9945fe78a75 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 18:39:47 +0530 Subject: [PATCH 14/15] chore(ci): remove deploy dry-run test paths Finalize deploy workflow for master push releases by removing workflow_dispatch dry-run mode and related conditional test scaffolding. Co-authored-by: Cursor --- .github/workflows/deploy.yml | 39 ++---------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 93d0d50aa..235af56fe 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,16 +4,6 @@ on: push: branches: - master - workflow_dispatch: - inputs: - mode: - description: "dry-run (no publish) or release" - required: true - default: dry-run - type: choice - options: - - dry-run - - release permissions: contents: write @@ -28,10 +18,7 @@ jobs: name: Prepare for deploy workflow runs-on: ubuntu-latest outputs: - deploy_mode: ${{ steps.resolve_mode.outputs.deploy_mode }} - dryrun_version: ${{ steps.versions.outputs.dryrun_version }} release_version: ${{ steps.versions.outputs.release_version }} - active_version: ${{ steps.versions.outputs.active_version }} steps: - uses: actions/checkout@v4 with: @@ -52,37 +39,16 @@ jobs: path: node_modules key: ${{ runner.os }}-deploy-node-modules-${{ github.run_id }}-${{ hashFiles('package-lock.json') }} - - name: Resolve deploy mode - id: resolve_mode - run: | - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "deploy_mode=${{ inputs.mode }}" >> "$GITHUB_OUTPUT" - elif [ "${{ github.event_name }}" = "push" ] && [ "${{ github.repository }}" = "webex/react-widgets" ]; then - echo "deploy_mode=release" >> "$GITHUB_OUTPUT" - else - echo "deploy_mode=dry-run" >> "$GITHUB_OUTPUT" - fi - - - name: Set dry-run version + - name: Set release version id: versions - env: - DEPLOY_MODE: ${{ steps.resolve_mode.outputs.deploy_mode }} run: | - BASE_VERSION="$(node -p "require('./package.json').version")" RELEASE_VERSION="$(node -e "const semver = require('semver'); const v = require('./package.json').version; process.stdout.write(semver.inc(v, 'minor'));")" - echo "dryrun_version=${BASE_VERSION}-dryrun.${{ github.run_number }}" >> "$GITHUB_OUTPUT" echo "release_version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" - if [ "${DEPLOY_MODE}" = "release" ]; then - echo "active_version=${RELEASE_VERSION}" >> "$GITHUB_OUTPUT" - else - echo "active_version=${BASE_VERSION}-dryrun.${{ github.run_number }}" >> "$GITHUB_OUTPUT" - fi build_cdn: name: Build For CDN runs-on: ubuntu-latest needs: prepare - if: ${{ needs.prepare.outputs.deploy_mode == 'release' }} env: WEBEX_SCOPE: Identity:OAuthClient webexsquare:get_conversation webexsquare:admin spark:people_read spark:rooms_read spark:rooms_write spark:memberships_read spark:memberships_write spark:messages_read spark:messages_write spark:applications_read spark:applications_write spark:teams_read spark:teams_write spark:team_memberships_read spark:team_memberships_write spark:bots_read spark:bots_write spark:kms VERSION_NUMBER: ${{ needs.prepare.outputs.release_version }} @@ -129,7 +95,6 @@ jobs: runs-on: ubuntu-latest needs: - prepare - if: ${{ needs.prepare.outputs.deploy_mode == 'release' }} env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} steps: @@ -176,7 +141,7 @@ jobs: needs: - prepare - build_cdn - if: ${{ needs.prepare.outputs.deploy_mode == 'release' && needs.build_cdn.result == 'success' }} + if: ${{ needs.build_cdn.result == 'success' }} env: AWS_BUCKET: code.s4d.io AWS_REGION: us-east-1 From f99a57f4b4ed61909caac9e8963b6302a02080a6 Mon Sep 17 00:00:00 2001 From: Shreyas Sharma Date: Mon, 1 Jun 2026 18:43:10 +0530 Subject: [PATCH 15/15] chore(docs): remove outdated pipeline guide Delete the stale pipeline migration guide now that workflow behavior has been consolidated directly in the deploy and pull-request workflows. Co-authored-by: Cursor --- .github/pipeline-guide.md | 125 -------------------------------------- 1 file changed, 125 deletions(-) delete mode 100644 .github/pipeline-guide.md diff --git a/.github/pipeline-guide.md b/.github/pipeline-guide.md deleted file mode 100644 index e14243931..000000000 --- a/.github/pipeline-guide.md +++ /dev/null @@ -1,125 +0,0 @@ -# GitHub Actions Pipeline Guide - -This repository now runs CI/CD in GitHub Actions and keeps CircleCI only as historical reference until final decommissioning. - -## Source Of Truth Files - -- [`.github/workflows/pull-request.yml`](.github/workflows/pull-request.yml) -- [`.github/workflows/deploy.yml`](.github/workflows/deploy.yml) -- [`.circleci/config.yml`](.circleci/config.yml) (reference for legacy parity) - -## CI/CD Flow At A Glance - -```mermaid -flowchart TD - prEvent[PullRequestEvent] --> prValidate[ValidateLabel] - prValidate --> prInstall[InstallDependencies] - prInstall --> prUnit[UnitAndLint] - prInstall --> prBuild[BuildForIntegration] - prBuild --> prChrome[IntegrationChrome] - prBuild --> prFirefox[IntegrationFirefox] - - pushEvent[PushMaster] --> depInstall[InstallDependencies] - depInstall --> depRelease[VersionRelease] - depRelease --> depBuildNpm[BuildForNpm] - depBuildNpm --> depBuildCdn[BuildForCdn] - depBuildCdn --> depPublishNpm[PublishToNpm] - depPublishNpm --> depSyncCdn[SyncToS3] - depSyncCdn --> depInvalidate[CloudFrontInvalidationOptional] -``` - -## Trigger Model - -### Pull Request CI (`pull-request.yml`) - -- Event: `pull_request` on `opened`, `reopened`, `synchronize`, `labeled`, `unlabeled`. -- Manual support: `workflow_dispatch`. -- Concurrency: `${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}` with cancel-in-progress. -- Critical gate: `validated` label is required for PR checks to proceed. - -### Deploy CD (`deploy.yml`) - -- Event: `push` to `master`. -- Manual support: `workflow_dispatch`. -- Concurrency: one deploy per ref; queue rather than cancel. - -## Pull Request Pipeline Breakdown - -### Job Order - -1. `validate` - fails if `validated` label is absent. -2. `install` - `npm ci`. -3. `unit_tests_and_linting` - eslint + jest. -4. `build_for_tests` - builds `dist-test`. -5. `journey_tests_chrome` - integration suite in Chrome. -6. `journey_tests_firefox` - integration suite in Firefox. - -### Secrets Used In PR Workflow - -- `WEBEX_APPID_ORGID` -- `WEBEX_APPID_SECRET` -- `WEBEX_CLIENT_ID` -- `WEBEX_CLIENT_SECRET` -- `SKIP_FLAKY_TESTS` - -All secrets are read from GitHub Actions `secrets.*` and are never hardcoded. - -## Deploy Pipeline Breakdown - -### Job Order - -1. Checkout and `npm ci`. -2. `npm run release -- --release-as minor --no-verify`. -3. Build for npm (`build:packagejson`, `build:transpile all`). -4. Build CDN bundles for: - - `@webex/widget-space` - - `@webex/widget-recents` - - `@webex/widget-demo` -5. Push release commit and tags. -6. Publish npm packages (`npm run publish:components`). -7. Sync built artifacts to S3 for `alpha`, `latest`, and versioned `archives/`. -8. Optionally run CloudFront invalidation when distribution ID is configured. - -### Secrets Used In Deploy Workflow - -- `NPM_TOKEN` -- `AWS_ACCESS_KEY_ID` -- `AWS_SECRET_ACCESS_KEY` -- `AWS_DISTRIBUTION_ID` (optional) - -## Migration Notes From CircleCI - -Migrated from CircleCI: - -- `install` -- `unit_tests_and_linting` -- `build_for_tests` -- `journey_tests_chrome` -- `journey_tests_firefox` -- `version_and_publish` -- `deploy_to_cdn` - -Not migrated in this phase: - -- `promotions` scheduled workflow -- `tap` scheduled workflow -- production tag promotion flow - -## Security And Compliance Notes - -- Never hardcode credentials in workflow files; use GitHub repository secrets. -- Keep least privilege: PR workflow is read-only; deploy workflow has write permissions only where needed. -- Avoid shell commands that print secrets (for example `echo $NPM_TOKEN`). -- Prefer `pull_request` over `pull_request_target` unless trusted-code execution requirements force otherwise. - -## Verification Checklist - -1. YAML syntax: - - `yamllint .github/workflows/*.yml` (if available) -2. PR checks: - - open PR without `validated` label and confirm `validate` fails - - add `validated` label and confirm all test jobs run -3. Deploy checks on `master`: - - npm publish step authenticates successfully - - S3 sync uploads to expected `alpha`, `latest`, and `archives/` paths - - CloudFront invalidates only when distribution ID secret is set