Skip to content

FLEXY-6498 Dual Webpack (4 & 5) Support - #1164

Open
shyamasish-twilio wants to merge 32 commits into
mainfrom
FLEXY-6498
Open

shyamasish-twilio wants to merge 32 commits into
mainfrom
FLEXY-6498

Conversation

@shyamasish-twilio

@shyamasish-twilio shyamasish-twilio commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Adds a parallel webpack 5 build/dev-server pipeline (@twilio/flex-plugin-webpack5) alongside the existing webpack 4 pipeline (@twilio/flex-plugin-webpack), selectable via a new --wp5 flag on build/start. Webpack 4 remains the default; nothing changes for existing plugins that don't pass the flag.

New package: @twilio/flex-plugin-webpack5

A new package mirroring flex-plugin-webpack's structure, built on webpack 5 (aliased as webpack5 in package.json so both majors coexist in one node_modules tree). Includes:

  • webpack.config.ts / webpack.dev.ts — static (shell) / JavaScript (plugin) / complete configurations, using fork-ts-checker-webpack-plugin@9 (upgraded from the v4 API used by webpack 4) with project-aware TypeScript resolution — prefers the target plugin's own installed TypeScript over the tool's bundled copy.
  • compiler.ts, devServer/{ipcServer,pluginServer,webpackDevServer}.ts — dev-server orchestration, IPC coordination between the "start flex" (host shell) and "start plugin" (JS bundle) sub-processes, mirroring webpack 4's architecture.
  • clientVariables.ts, prints/*, plugins/DelayRenderStaticPlugin.ts — supporting utilities.
  • Full test suite (40 tests, ipcServer, pluginServer, webpack.config, clientVariables).

CLI / orchestration changes

  • plugin-flex/src/commands/flex/plugins/{build,start}.ts: new --wp5 boolean flag. start.ts now correctly propagates --wp5 to every spawned start sub-process — previously only the "flex" host shell received it, so the actual plugin bundle silently always built on webpack 4 regardless of the flag.
  • flex-plugin-scripts/src/scripts/{build,start}.ts: branch on --wp5 to run the webpack5 build/dev-server path (_runWebpack5, _startDevServerWp5) instead of the webpack 4 path.
  • flex-plugin-scripts/src/config/index.ts: new getConfigurationForWp5(), parallel to the existing getConfiguration(), resolving webpack/dev-server/jest configs from the wp5 package.
  • Sourcemap file-count fix: webpack 5's Stats schema nests a chunk's sourcemap inside that asset's own related array instead of listing it as an independent top-level asset (unlike webpack 4). Added _flattenAssetsWp5() in build.ts so the printed build summary correctly counts .map files.

E2E test coverage

Added a parallel Node 24 / webpack5 e2e suite alongside the existing Node 22/webpack4 one, without altering the latter's behavior:

  • flex-plugin-e2e-tests: scenario.wp5 (driven by WP5 env var) threads --wp5 through every build/start CLI invocation in the test steps.
  • ~reusable_e2e_all_OS.yaml / ~reusable_e2e_by_OS.yaml: new optional NODE_VERSION/WP5 inputs, defaulted to current behavior (additive only).
  • New e2e_scheduled_wp5.yaml (nightly) and pr_e2e_wp5.yaml (PR label-triggered), mirroring the existing scheduled/PR workflows on Node 24 with --wp5.

Requires manual setup outside this PR

pr_e2e_wp5.yaml references new repo/org GitHub Actions variables (E2E_WP5_LABEL, PUBLISH_AND_E2E_WP5_LABEL) and corresponding PR labels — these need to be created in repo settings before that workflow can trigger.

Test plan

  • npm run build at root — webpack 4 and webpack 5 paths both compile
  • twilio flex:plugins:build (default, webpack 4) — unchanged behavior
  • twilio flex:plugins:build --wp5 — builds via webpack 5, .js/.js.map both emitted and counted
  • twilio flex:plugins:start --wp5 — both "flex" and "plugin" sub-processes run on webpack 5
  • flex-plugin-webpack5 unit tests: 40/40 passing
  • Fresh npm install from clean node_modules succeeds without --force/--legacy-peer-deps
  • New e2e workflows validated (YAML syntax checked; live run pending label/var setup)

Contributing to Twilio

All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under.

  • I acknowledge that all my contributions will be made under the project's license.

shyamasish-twilio and others added 6 commits June 11, 2025 14:53
# Conflicts:
#	lerna.json
#	package-lock.json
#	packages/flex-plugin-scripts/package.json
…2 suite

Adds a parallel e2e track (nightly scheduled + PR label-triggered) that
runs on Node 24 and builds/starts plugins with --wp5, without changing
the behavior of the existing Node 22/webpack4 suite.

- Thread scenario.wp5 (from WP5 env var) through build/start test steps
  to append --wp5 to the CLI invocations
- Add optional NODE_VERSION/WP5 inputs to the reusable e2e workflows,
  defaulting to current behavior so existing callers are unaffected
- Add e2e_scheduled_wp5.yaml (nightly) and pr_e2e_wp5.yaml (PR label)
  mirroring the existing scheduled/PR workflows

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lyTyped patch releases

An unbounded "@types/node": "*" wildcard pulled in by jest-circus can
resolve to a newest-on-registry @types/node patch release containing
TS-syntax that pre-TS5 compilers can't parse (e.g. ffi.d.ts's "const
type parameters"), crashing type-checking in scaffolded plugins.

- Pin an exact, verified-compatible @types/node version as a dependency
  (not devDependency, since only dependencies reach consumers) in
  flex-plugin-test, so its own nested copy stabilizes
- Disable TypeScript's automatic @types/* acquisition
  ("types": []) in the create-flex-plugin TS templates, since
  scaffolded plugins run in the browser and never reference Node
  globals - this narrows exposure to the wider class of unrelated
  @types/* packages breaking production type-checking

Note: this does not fully eliminate the crash for an already-scaffolded
project, since explicit `/// <reference types="node" />` directives in
other dependencies' bundled .d.ts files still resolve whichever
@types/node ends up hoisted to the project's own root node_modules,
which this change does not control.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment thread .github/workflows/pr_e2e_wp5.yaml Outdated
if: always()
runs-on: ubuntu-22.04
steps:
- uses: actions-ecosystem/action-remove-labels@v1

@semgrep-code-twilio semgrep-code-twilio Bot Sep 23, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An action sourced from a third-party repository on GitHub is not pinned to a full length commit SHA. Pinning an action to a full length commit SHA is currently the only way to use an action as an immutable release. Pinning to a particular SHA helps mitigate the risk of a bad actor adding a backdoor to the action's repository, as they would need to generate a SHA-1 collision for a valid Git object payload.

🍰 Fixed in commit f3e2b54 🍰

Comment thread .github/workflows/pr_e2e_wp5.yaml Outdated
if: always()
runs-on: ubuntu-22.04
steps:
- uses: actions-ecosystem/action-remove-labels@v1

@semgrep-code-twilio semgrep-code-twilio Bot Sep 23, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-github-action compromises. Pin the reference to a full 40-character commit SHA instead, e.g. uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608.

🚀 Fixed in commit f3e2b54 🚀

Comment thread .github/workflows/pr_e2e_wp5.yaml Outdated
version: ${{ steps.alphaVersion.outputs.version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3

@semgrep-code-twilio semgrep-code-twilio Bot Sep 23, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-github-action compromises. Pin the reference to a full 40-character commit SHA instead, e.g. uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608.

⭐ Fixed in commit f3e2b54 ⭐

Comment thread .github/workflows/pr_e2e_wp5.yaml Outdated
branch: ${{ steps.extractBranch.outputs.branch }}
version: ${{ steps.alphaVersion.outputs.version }}
steps:
- uses: actions/checkout@v4

@semgrep-code-twilio semgrep-code-twilio Bot Sep 23, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GitHub Actions step uses a mutable tag or branch reference. Tags and branch names can be silently repointed by the action owner, enabling supply-chain attacks — as seen in the trivy-action and kics-github-action compromises. Pin the reference to a full 40-character commit SHA instead, e.g. uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608.

🧁 Fixed in commit f3e2b54 🧁

shyamasish-twilio and others added 5 commits September 23, 2026 09:56
…allow-list policy

The org now enforces that workflow actions be pinned to a full-length
commit SHA (flex-monorepo's publish.yml, the only surviving GH Actions
workflow there post-enforcement, already does this). Every action
reference across the workflows was still using a floating version tag
(@V3, @v4, etc.), causing every PR-triggered workflow - including
pr_workflow.yaml, unrelated to this branch's other changes - to fail
at startup with "action not allowed".

- actions/checkout and actions/setup-node pinned to the exact SHAs
  flex-monorepo's publish.yml already uses successfully under this
  same policy (setup-node bumped v3 -> v6 to match, since only that
  SHA is confirmed to pass)
- actions/upload-artifact, actions/create-github-app-token pinned to
  current tag SHAs (GitHub-authored, same trust class as checkout/
  setup-node)
- codecov/codecov-action, rtCamp/action-slack-notify,
  actions-ecosystem/action-remove-labels pinned to current tag SHAs,
  but these are third-party and not confirmed on the org's allow-list -
  may still need an org admin to add them explicitly

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The previously-pinned v3 SHA is a third-party action not on the org's
allow-list by name/version, so pinning alone didn't unblock it (unlike
actions/checkout and actions/setup-node, which are GitHub-authored and
passed once pinned). 0cfda1dd0a4ad9efc75517f399d859cd1ea4ced1 (v4.0.2)
is confirmed allow-listed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
codecov/codecov-action is a third-party action not covered by any of
the org's GitHub Actions allow-list patterns, by name or SHA - pinning
to a specific commit (even a supposedly-approved one) still fails with
the same "action not allowed" error. Since there's no repo-level fix
for this (it requires an org admin to explicitly allow-list the
action), removing the step is the only way to unblock the check right
now. Coverage reporting can be re-added once codecov-action is
allow-listed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
flex-monorepo's publish.yml (the only surviving GH Actions workflow
there post org-enforcement) runs on ubuntu-x64 rather than
ubuntu-22.04/ubuntu-latest. Align every Linux job across all workflows
to the same label for consistency; windows-latest/macos-latest in the
e2e OS matrix are unchanged since they're not Ubuntu runners.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… runner labels

Root cause of the npm ci ETIMEDOUT failures: the org locked down direct/
implicit network access to the npm Artifactory proxy that npm ci relied
on, as part of the same enforcement wave that introduced the Actions
allow-list. Reaching it now requires exchanging the job's GitHub OIDC
token for a short-lived Artifactory access token first.

Ported and completed the pattern from the (unfinished) lockdown-migration
branch:
- Added .github/actions/artifactory-oidc, a composite action that
  exchanges the OIDC token for an Artifactory token and configures npm
  via ~/.npmrc. lockdown-migration authored this action but never
  actually wired it into any workflow step - added the missing
  `uses: ./.github/actions/artifactory-oidc` step before every npm ci,
  and the `permissions: id-token: write` each job needs to request the
  token.
- Adopted their runner-label split: `ubuntu-x64` for jobs that install
  and publish (pr_workflow.yaml build-and-test, both publish reusable
  workflows), `ubuntu-latest-large`/`macos-latest-large`/
  `windows-latest-large` for the e2e matrix and lightweight/skip jobs.
- Replaced rtCamp/action-slack-notify and actions-ecosystem/action-
  remove-labels with a plain curl POST / `gh pr edit --remove-label`,
  matching lockdown-migration - both are third-party actions outside
  the org's allow-list (same failure class as codecov-action, which
  stayed blocked even when pinned to a specific commit SHA).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Comment on lines +60 to +76
run: |
curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${{ job.status == 'success' && 'good' || 'danger' }}\",
\"title\": \"Flex Plugins CLI\",
\"text\": \"🎉 Released a new version with *tag* \\\`${{ inputs.TAG }}\\\` and *version* \\\`${{ inputs.VERSION }}\\\`\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"

@semgrep-code-twilio semgrep-code-twilio Bot Sep 23, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High severity issue identified in your code:

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

🧼 Removed in commit c26f4f3 🧼

Comment on lines +200 to +223
run: |
STATUS_COLOR="good"
if [ "${{ needs.node.result }}" = "failure" ]; then
STATUS_COLOR="danger"
elif [ "${{ needs.node.result }}" = "cancelled" ]; then
STATUS_COLOR="warning"
fi

curl -X POST "${{ secrets.SLACK_WEB_HOOK }}" \
-H 'Content-Type: application/json' \
-d "{
\"username\": \"Github Actions\",
\"icon_emoji\": \":ship:\",
\"attachments\": [{
\"color\": \"${STATUS_COLOR}\",
\"title\": \"${{ inputs.SLACK_TITLE }} - ${{ inputs.OS }} - ${{ inputs.NODE_VERSION }}\",
\"text\": \"${{ github.repository }}/${{ github.ref }} - ${{ inputs.SLACK_MESSAGE }} ${{ needs.node.result }} for ${{ inputs.OS }}.\",
\"actions\": [{
\"type\": \"button\",
\"text\": \"View Action\",
\"url\": \"${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}\"
}]
}]
}"

@semgrep-code-twilio semgrep-code-twilio Bot Sep 23, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High severity issue identified in your code:

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

🎈 Fixed in commit c26f4f3 🎈

shyamasish-twilio and others added 5 commits September 24, 2026 01:37
…ne gate

The branch lockfile had 512 entries resolved to
npmjs.artifacts.twilio.com (picked up from a local registry config while
regenerating), which external consumers cannot install from. Rewrote them
to registry.npmjs.org; tarball paths and integrity hashes are unchanged.

CI still authenticates with Artifactory via the artifactory-oidc action,
which redirects npm through ~/.npmrc at build time only. Committed files
must always point at the public registry (ADR 1634 Rule 3b), matching the
lockfile-hygiene pattern in twilio-agent-connect-typescript:
- .github/scripts/lockfile-hygiene.sh fails if any tracked lockfile or
  .npmrc names a Twilio Artifactory host.
- .github/workflows/lockfile_hygiene.yaml runs that scan plus a clean-room
  npm ci against registry.npmjs.org only. No paths-ignore, so
  lockfile-only changes are still checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… probe ejs/rfc6902

With Artifactory OIDC auth working, npm ci now reaches twilio.jfrog.io but
JFrog curation (security-block-critical-cve-cvss-9-to-10) returns 403 for
several transitive packages. The retry loop swallowed the failure, so the
step passed and lint later ran a global lerna v10 against a missing
node_modules ("useWorkspaces option has been removed").

- Retry loops in pr_workflow and the e2e/publish reusable workflows now
  exit 1 after the third failed npm ci instead of silently continuing.
- Add npm overrides moving blocked packages to fixed versions:
  axios 0.31.1 (under @twilio/flex-ui, twilio-taskrouter, twilio; root
  axios 1.x unchanged), flatted 3.4.x, form-data 3.0.x under jsdom,
  handlebars 4.7.9, immutable 4.3.x, ip 2.0.1, lodash-es 4.18.x,
  lodash.template 4.18.x, proxy-addr 2.0.8. Bump flex-plugin-e2e-tests
  axios to ^0.31.1. Lockfile regenerated; all resolved URLs point at
  registry.npmjs.org.
- TEMPORARY: probe step reporting which ejs and rfc6902 versions curation
  allows. ejs (no upstream fix for CVE-2023-29827) and rfc6902 (fix only
  in 5.x) are still blocked and need a version decision or exception.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The earlier probe showed JFrog curation only allows ejs@6.0.1 and
rfc6902@5.x. Both are API-compatible with how they are used here
(@oclif/core and @nrwl/devkit only call ejs.render; twilio-chat only uses
applyPatch/createPatch/createTests), so override them. ejs 6 also drops
jake/filelist/async from the tree.

axios@0.31.1 (suggested by curation as the fix) is itself blocked, so the
suggested fix versions are not reliable. Replace the ejs/rfc6902-only
probe with a TEMPORARY script that scans every tarball in the lockfile
against the curated registry and probes candidate versions for each
overridden package, so all remaining blocks show up in one run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The curation probe showed every axios 0.27.x-0.34.x (including the
suggested 0.31.1) is blocked; 0.26.1 is the only allowed 0.x release.
Pin the nested 0.x copies (@twilio/flex-ui, twilio-taskrouter, twilio)
and flex-plugin-e2e-tests' direct dependency to 0.26.1. Root axios 1.20.0
is allowed and unchanged. All other overridden packages were already on
allowed versions.

Type the style loaders and JS plugins arrays in the webpack5 config.
Under tsconfig.test.json (noImplicitAny: false) the untyped [] literals
were inferred as never[], failing webpack.config.test.ts in the full
test run and stopping bin/test.js before the remaining packages ran.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ipts coverage

With npm ci now passing, test:ci ran coverage thresholds for the first
time on this branch and flex-plugin-scripts failed them (statements/lines
91.83% < 94%, functions 91.76% < 92%): the webpack 5 paths added for
dual-webpack support had no tests.

- build.test.ts: --wp5 routing, _flattenAssetsWp5, _handlerWp5
  (error, compile errors, success), and _runWebpack/_runWebpack5 with a
  mocked webpack.
- start.test.ts: --wp5 routing in start (incl. process error handlers),
  _startDevServerWp5 (plugin server, IPC server/client, compiler
  callbacks, crash emit), _onServerCrashWp5,
  _getPluginsConfigurationWp5, _requirePackages.

flex-plugin-scripts is now at 96.48% statements/lines and 98.82%
functions; build.ts and start.ts are at 100% line coverage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added the run-e2e Trigger the mandatory E2E tests for Pull request label Sep 23, 2026
…probe

- artifactory-oidc: write always-auth=true to ~/.npmrc. The e2e tests
  run `twilio plugins:install`, which installs through the yarn 1 bundled
  in twilio-cli. Yarn 1 only sends the _authToken when always-auth is
  set, so Artifactory returned 401 and yarn reported every package as
  "Couldn't find package ... on the npm registry". npm already sent the
  token and is unaffected.
- Switch the macOS e2e runner label from macos-latest-large (never
  picked up; jobs queued until the 24h timeout) to macos-latest in the
  PR e2e matrix, nightly workflows, skip_e2e, and the reusable workflow's
  OS checks.
- Remove the temporary curation probe step and script; the full
  lockfile scan reported 0 blocked tarballs and build/test are green.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Sep 23, 2026
`twilio plugins:install @twilio-labs/plugin-flex` installs with yarn into
the Twilio CLI data directory, resolving against that directory's own
package.json, so the repo's npm overrides do not apply. The published
plugin pulls ejs@^3 (via @twilio/cli-core -> @oclif/core@1) and
Artifactory curation blocks every ejs below 6.0.1, failing step001 with
a 403 on ejs-3.1.10.tgz.

- Pin the CLI data directory via TWILIO_DATA_DIR in the shared spawn env
  so every twilio invocation uses the same plugins directory (the spawn
  helper builds a minimal env and does not forward process.env).
- step001 seeds that directory's package.json with
  "resolutions": { "ejs": "^6.0.1" } before plugins:install;
  @oclif/plugin-plugins preserves extra fields when it saves.

Verified locally: plugins:install of plugin-flex@7.1.2 resolves
ejs@^3.1.6 to 6.0.1, and `twilio flex:plugins --help` still renders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Sep 23, 2026
…ted plugins

After pinning ejs, `twilio plugins:install` now fails on a different
curation block (undici-5.29.0, via @twilio/cli-core -> @actions/core ->
@actions/http-client). To stop discovering one blocked package per run,
add a TEMPORARY Linux-only probe step to the reusable e2e workflow that
checks every package version the e2e installs outside this repo's
lockfile against the curated registry:

- curation-targets.json: 1974 name@version entries generated locally
  from `twilio plugins:install @twilio-labs/plugin-flex@7.1.2` (with the
  ejs resolution) plus `npm i` in JS and TS plugins created by
  `twilio flex:plugins:create` with @twilio/flex-ui@latest.
- curation-probe.js: reports each blocked version with its source and
  probes the newest same-major and higher-major releases as candidates.

Remove both files and the step once the e2e installs pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added the run-e2e Trigger the mandatory E2E tests for Pull request label Sep 23, 2026
…probe

The e2e curation probe (1974 package versions across
`twilio plugins:install @twilio-labs/plugin-flex@7.1.2` and `npm i` in
the created JS/TS plugins) found a single blocked version: undici@5.29.0,
via @twilio/cli-core -> @actions/core -> @actions/http-client@2.2.3.
Allowed: 6.28.1, 7.29.1, 8.10.2.

- step001: add undici ^6.28.1 to the plugins-dir yarn resolutions.
  undici 6 is what @actions/http-client@3+ uses and still provides the
  ProxyAgent http-client@2 relies on (verified locally: plugin installs,
  getAgentDispatcher returns a ProxyAgent, @actions/core loads).
- Remove the temporary probe step, script and target list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Sep 23, 2026
…grades

Nine of the thirteen root overrides never changed resolution: the parent's
own declared range already resolves to the pinned version, so they only
existed because the lockfile was stale. Confirmed with a clean from-scratch
resolve, which produced the same versions without them (flatted 3.4.4,
form-data 3.0.5, handlebars 4.7.9, immutable, ip, lodash-es 4.18.1,
lodash.template 4.18.1, proxy-addr 2.0.8, twilio -> axios 0.26.1).

The remaining four do force a version outside the parent's range and stay
for now: ejs, rfc6902, and axios under @twilio/flex-ui and twilio-taskrouter.

Add .github/scripts/curation-probe.js to the PR validator to report which
published versions of the relevant parents curation actually allows, and
what each declares for the deps we pin. Curation is only enforced against
the Artifactory registry under the workflow's OIDC identity -- reads from a
laptop are anonymous and ungated, so they always report "allowed" and the
question can only be answered from CI. The step only reports; it does not
gate the build. workflow_dispatch is added because this workflow's
paths-ignore skips package.json / package-lock.json changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio removed the run-e2e Trigger the mandatory E2E tests for Pull request label Sep 25, 2026
shyamasish-twilio and others added 2 commits September 28, 2026 02:52
The lockfile hygiene gate (ADR 1634 Rule 3b) only detects Artifactory hosts
in a committed lockfile and fails closed; remediation is manual. A lockfile
generated on a machine pointed at Artifactory therefore reaches CI broken,
and npm resolves the registry from an env var that overrides --userconfig,
so "resolve against public npm" is easy to get wrong by accident.

Rewrite it at the source instead: .github/scripts/public-lockfile.js swaps
the https://<twilio-artifactory-host>/artifactory/api/npm/<repo>/ prefix for
https://registry.npmjs.org/ in any staged lockfile and re-stages it. It uses
the same host patterns as lockfile-hygiene.sh so the fixer and the gate
cannot drift, and no-ops when no lockfile is staged.

integrity is left alone, which holds while Artifactory proxies byte-identical
tarballs, and a package published only to Artifactory would 404 publicly.
Neither is detectable offline, so CI's clean-room public install stays the
backstop -- this hook is the ergonomic fix, not the guarantee.

Note: these two files are identical to those in aba4701 on origin, which is
deliberately not merged into this branch state.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Flex UI 1.x is end of life, so plugins can no longer be created for it and
the package should not appear in this repo's dependency tree at all.

Remove the v1 authoring path:
- delete templates/js and templates/ts; js2/ts2 are now the only templates
- drop the @twilio/flex-ui devDependency from create-flex-plugin, which was
  not imported but read back as a version constant for --flexui1
- --flexui1 is still registered so it fails with guidance pointing at
  `twilio flex:plugins:upgrade-plugin` rather than yargs' "Unknown argument";
  --flexui2 stays as an accepted no-op since v2 is the only mode

upgrade-plugin's v1 -> v2 migration is deliberately untouched: it is how
customers get off Flex UI 1, and removing it would strand them. The
version-agnostic references (fs.ts path resolution, getLatestFlexUIVersion,
telemetry) are likewise unchanged.

flex-plugin still needs flex-ui for the FlexGlobal/Flex.Manager types, so its
devDependency moves to ^2. That requires TypeScript >= 4.5: flex-ui 2.x ships
inline type modifiers (`import { type X }`). 4.9.5 stays under ts-jest@27's
`typescript <5.0` peer, so the jest toolchain, @types/node and
suppressImplicitAnyIndexErrors are all untouched. useUnknownInCatchVariables
is disabled to keep the pre-4.4 behaviour for ~94 existing catch blocks.

With flex-ui on v2 the tree no longer contains rfc6902 or axios 0.21.x, so
three of the four overrides are gone; only ejs remains, which is blocked on
@twilio/cli-core moving off @oclif/core 1.x upstream.

Also fixed along the way:
- @oclif/parser generic constraints in parser.ts and flex-plugin.ts; without
  them F degrades to `unknown` and every flags[key] access fails to compile
- two mockReturnThis() mocks whose return value is consumed; TS now emits
  `(0, mod.fn)()`, so `this` is undefined rather than the module object
- create-flex-plugin's test cleanup guarded itself with fs.existsSync, which a
  later test spies on and resetAllMocks() leaves returning undefined, silently
  leaking plugin-test/ into the next run

Verified: typecheck 0 errors across 13 packages, build exit 0, 1158 tests
passing across 12 packages with coverage thresholds met.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The OIDC token minted at the start of the job expires after roughly ten
minutes. Several tests reach the registry -- setupConfiguration calls
getLatestFlexUIVersion, which goes through package-json and got -- so once
npm ci and lint push the test step past that window those lookups return
401 Unauthorized.

Evidence from three runs of this workflow, measured from the auth step:

  6211b38  create-flex-plugin tests at  6m56s  -> passed
  2ce1218  create-flex-plugin tests at  9m23s  -> passed
  2ce1218  plugin-flex tests at        10m47s  -> 401
  2ce1218  create-flex-plugin tests at 10m23s  -> 401  (re-run, same commit)

The same commit failing in two different places on two runs is what
identifies this as a timing boundary rather than a code change; the
dependency tree for package-json, got, registry-auth-token and registry-url
is byte-identical between 6211b38 and 2ce1218.

Refresh the token immediately before the test step instead of racing it.
This is a mitigation: the underlying problem is that those tests are not
hermetic and depend on a live registry. Making them mock
getLatestFlexUIVersion remains worth doing separately.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added the run-e2e Trigger the mandatory E2E tests for Pull request label Sep 27, 2026
Same ~10 minute OIDC token expiry as d4a969b, now in the e2e workflow. The
ubuntu job authenticated at 22:19:01, reached the e2e step at +9m21s and
yarn began reporting every package as missing at +10m34s -- which is yarn 1's
rendering of a 401, as already documented in 66382eb.

Registry access is front-loaded: of the 13 steps only step001
(twilio plugins:install) and step002 (flex:plugins:create, which installs
dependencies for each generated plugin) reach the registry; the npm i in
step003 is commented out and steps 004-013 are build/start/browser work.
Refreshing the token immediately before each of the JS and TS invocations
therefore covers the part of the run that needs it.

This is a stopgap, not a fix. Observed e2e jobs run 281 minutes (ubuntu) and
318 minutes (windows), so a token minted before the step is dead for most of
the run, and a slow enough plugins:install can still outlive it. The durable
fix is a longer TTL on the Artifactory OIDC provider, which is platform
configuration rather than a repo change and affects any Twilio job whose
registry access extends past ten minutes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Sep 27, 2026
Drop the Artifactory OIDC authentication from the e2e workflow, along with
the two re-auth steps added in 48897c2 and the now-unused id-token
permission and ARTIFACTORY_URL. With no ~/.npmrc written, npm and the yarn 1
bundled in twilio-cli both resolve against registry.npmjs.org, which the
committed lockfile already points at (enforced by the lockfile hygiene gate).

This removes the token expiry problem at the root rather than racing it. The
OIDC token lasts about ten minutes; npm ci alone took 9m07s against its own
10m timeout, and the suite then runs for hours, so every mitigation was a
question of when rather than whether it failed:

  auth issued      22:19:01
  npm ci finished  22:28:09   (9m07s, 53s of headroom)
  e2e step began   22:28:22   (token age 9m21s)
  first 401        22:29:35   (token age 10m34s)

The trade-off is deliberate and worth stating plainly: e2e installs are no
longer covered by Artifactory curation, so the supply chain gate does not
apply to what this workflow pulls. The argument for accepting that is that
e2e installs published packages and scaffolds plugins exactly as a customer
does, and customers resolve from public npm -- so this is closer to the
behaviour under test. Build, lint, unit tests and publish all still go
through Artifactory; only e2e changes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Sep 28, 2026
Two follow-ons from moving e2e to public npm in 300cfd1.

1. Remove the TWILIO_DATA_DIR pin and yarn resolutions seeding added in
   8114b40 and 4aca282. Those existed only to force ejs and undici to
   curation-allowed versions during `twilio plugins:install`; on public npm
   nothing is blocked, so they are dead weight.

   They were also actively breaking the run. The pin redirected the CLI to
   ~/twilio-cli-data, but nothing else honours that variable -- both
   flex-dev-utils/src/fs.ts:181 and plugin-flex's sub-commands/flex-plugin.ts:189
   hardcode ~/.twilio-cli. So the CLI wrote its plugins to one directory while
   `flex-plugin pre-script-check`, running as the generated plugin's
   postinstall, read from another that was never created:

     Error: ENOENT: no such file or directory, open '/home/runner/.twilio-cli/package.json'

   That mismatch was latent since 8114b40; the job previously failed earlier
   on a 401, so it never surfaced. Removing the pin puts the CLI back on its
   default directory, which is the one the readers expect.

2. remove-label-on-failure ran `gh pr edit` with neither a checkout nor
   --repo, so gh could not resolve the repository:

     failed to run git: fatal: not a git repository

   The job is `if: always()` with an inner `needs.e2e-test.result != 'success'`,
   and a skipped job is not a success, so it fired on every unrelated label
   event and failed -- stripping the label as a side effect. Pass
   --repo ${{ github.repository }} in both pr_e2e.yaml and pr_e2e_wp5.yaml.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Sep 28, 2026
@github-actions github-actions Bot removed the run-e2e Trigger the mandatory E2E tests for Pull request label Sep 28, 2026
The e2e JS run failed in flex-plugin pre-script-check with TS1005 parse
errors throughout @types/node/ffi.d.ts. @types/node@26.6.3 ships that file
and its typesVersions only provides fallbacks down to "<=5.6", so a
TypeScript below 5.6 matches no fallback, receives the newest typings and
cannot parse them. Generated plugins compile with TypeScript 4.9.5 -- via
flex-plugin-scripts for the JS template, and the template's own
"typescript": "^4" for the TS one.

Neither existing guard helps: the templates set "types": [] (irrelevant,
@types/node is reached through the import graph) and skipLibCheck: true,
which suppresses type errors in declaration files but not parse errors.

Pin @types/node to ^22 in both js2 and ts2, matching what this repo uses
since 2ce1218. Probed against TypeScript 4.9.5: ^20 (20.19.43), ^22
(22.20.4) and ^24 (24.19.0) all parse cleanly and none contain ffi.d.ts;
only 26.x does.

This is a dam rather than a fix. A top-level devDependency only wins while
other requesters use loose ranges -- if something in a plugin's tree later
demands @types/node ^26 explicitly, npm will nest it and the parse errors
return. The durable fix is moving the templates to "typescript": "^5", which
changes what plugin authors build with and so wants deciding on its own.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added the run-e2e Trigger the mandatory E2E tests for Pull request label Sep 28, 2026
@github-actions github-actions Bot removed the run-e2e Trigger the mandatory E2E tests for Pull request label Sep 28, 2026
shyamasish-twilio and others added 3 commits September 29, 2026 10:08
flex-plugin-webpack5 declared ^14 || ^16 || ^18 while CI builds it on Node 22
and, in the webpack 5 workflows, Node 24 -- so every install logged
EBADENGINE for the one package those workflows exist to exercise:

  npm warn EBADENGINE Unsupported engine {
  npm warn EBADENGINE   package: '@twilio/flex-plugin-webpack5@7.1.2',
  npm warn EBADENGINE   required: { node: '^14 || ^16 || ^18' },
  npm warn EBADENGINE   current: { node: 'v22.23.2', npm: '10.9.8' }

The other six packages declared ^16 || ^18 || ^20 || ^22, which also omitted
the Node 24 that pr_e2e_wp5.yaml and e2e_scheduled_wp5.yaml run on, so no
package claimed support for a version CI actively uses.

Set all seven to the same ^18 || ^20 || ^22 || ^24: adds 24, drops only the
long-EOL 16, and keeps 18 and 20 so existing consumers are unaffected.
engines is advisory rather than enforced (npm warns unless engine-strict is
set), so this changes no resolution or build behaviour -- it makes the
declared support match where the packages are actually built and tested.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
remove-label-on-failure gated its step on `needs.e2e-test.result != 'success'`,
which is also true when e2e-test is skipped or cancelled. Both e2e workflows
trigger on `pull_request: [labeled]` with no path or label filter at the
workflow level, so every label added to a PR starts a run; e2e-test then skips
because the label does not match, the condition holds, and the job removes
whichever label was just added.

That was masked until 78c2443: `gh pr edit` had neither a checkout nor
--repo, so it failed with "fatal: not a git repository" and removed nothing.
Adding --repo made the job work, which turned a harmless failure into an
active label remover -- it stripped an unrelated FLEXY-6498 label from #1164
on the next run. Fixing the symptom first was the wrong order.

Gate on `== 'failure'` so skipped and cancelled runs leave labels alone.

Note, separately: pr_e2e_wp5.yaml gates get-version on vars.E2E_WP5_LABEL and
vars.PUBLISH_AND_E2E_WP5_LABEL, neither of which is defined on the repository
(only E2E_LABEL and PUBLISH_AND_E2E_LABEL exist). Both comparisons are against
an empty string, so the webpack 5 e2e can never trigger. That needs either the
variables defining in repository settings or the workflow pointing at the
existing labels, and is left out of this change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pr_e2e_wp5.yaml gated every job on vars.E2E_WP5_LABEL and
vars.PUBLISH_AND_E2E_WP5_LABEL, neither of which is defined on the
repository -- only E2E_LABEL and PUBLISH_AND_E2E_LABEL exist. Both
comparisons were therefore against an empty string, which no label name can
equal, so get-version always skipped and e2e-test skipped with it. The
webpack 5 e2e has never been able to run.

Gate it on E2E_LABEL (run-e2e) and drop the publish path entirely:

- get-version no longer computes an alpha version, and no longer exports it
- release-alpha-version is removed
- PACKAGE_VERSION is always 'latest'

Reusing PUBLISH_AND_E2E_LABEL here would have raced pr_e2e: both workflows
derive the alpha as X.Y.(Z+1)-alpha.$(date '+%Y%m%d%H%M') from lerna.json at
minute granularity, so both would publish an identical version and the second
would fail because it already exists on npm. Running on run-e2e only is
deterministic.

The trade-off is that the webpack 5 suite always exercises the released
plugin rather than branch code. That suits what it is for -- checking
webpack 5 against a published plugin -- and pr_e2e with publish-and-e2e
remains the way to test an unpublished branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@shyamasish-twilio shyamasish-twilio added run-e2e Trigger the mandatory E2E tests for Pull request and removed run-e2e Trigger the mandatory E2E tests for Pull request labels Sep 29, 2026
Brings the branch up to date with main, which has moved on considerably:
the publish workflow consolidation and OIDC trusted publishing (#1167), the
dispatchable publish.yml and e2e.yml, the curated Artifactory install on the
PR pipeline, and the 7.1.3 version bump and changelog.

Fourteen files conflicted. Where both sides had solved the same problem, main's
implementation was taken, because it is the released one and the rest of the
tree is built around it:

  ~reusable_publish.yaml and ~reusable_public_publish.yaml are deleted, as main
  did. Every change this branch made to them -- ubuntu-x64, pinned action SHAs,
  id-token permissions, Artifactory auth, the retry with a real exit code, the
  inline curl Slack calls -- is already present in the consolidated publish.yml.

  pr_workflow.yaml takes main's artifactory-npm-auth.sh over this branch's
  composite action, so the mechanism matches publish.yml. The composite action
  itself stays: lockfile_hygiene.yaml still uses it.

  curation-probe.js takes main's version, which is a superset with a
  --scan-lockfile mode. This branch's copy reads a registry= line from ~/.npmrc
  that artifactory-npm-auth.sh deliberately never writes, so it could not work
  alongside the auth mechanism above.

Where this branch had made a deliberate change main did not, this branch wins:
windows-latest-large across the e2e matrix and skip_e2e, and the removal of the
node-18 localhost override, which is dead now that only node 22 and 24 are used.

The version conflicts resolve to main's 7.1.3, and flex-plugin-webpack5 -- which
exists only here -- is bumped to match, along with its pin in
flex-plugin-scripts.

package-lock.json was regenerated rather than hand-merged. Its fourteen
conflicts were over real dependency-tree differences, not version strings, and
resolving those by hand is not reliable. It now carries all thirteen workspace
packages at 7.1.3 and keeps the ejs ^6.0.1 override. Public npm is unreachable
from a Twilio laptop, so it was generated against Artifactory and rewritten with
.github/scripts/public-lockfile.js; every resolved URL names registry.npmjs.org.

Verified locally: npm ci installs the regenerated lockfile cleanly, lint passes,
no conflict markers remain, all seven changed workflows parse as YAML,
curation-probe.js passes node --check, and builds.test.ts passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant