FLEXY-6498 Dual Webpack (4 & 5) Support - #1164
shyamasish-twilio wants to merge 32 commits into
Conversation
# 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>
| if: always() | ||
| runs-on: ubuntu-22.04 | ||
| steps: | ||
| - uses: actions-ecosystem/action-remove-labels@v1 |
There was a problem hiding this comment.
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 🍰
| if: always() | ||
| runs-on: ubuntu-22.04 | ||
| steps: | ||
| - uses: actions-ecosystem/action-remove-labels@v1 |
There was a problem hiding this comment.
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 🚀
| version: ${{ steps.alphaVersion.outputs.version }} | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: actions/setup-node@v3 |
There was a problem hiding this comment.
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 ⭐
| branch: ${{ steps.extractBranch.outputs.branch }} | ||
| version: ${{ steps.alphaVersion.outputs.version }} | ||
| steps: | ||
| - uses: actions/checkout@v4 |
There was a problem hiding this comment.
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 🧁
…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>
| 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 }}\" | ||
| }] | ||
| }] | ||
| }" |
There was a problem hiding this comment.
🟠 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 🧼
| 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 }}\" | ||
| }] | ||
| }] | ||
| }" |
There was a problem hiding this comment.
🟠 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 🎈
…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>
…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>
`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>
…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>
…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>
…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>
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>
aba4701 to
2ce1218
Compare
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>
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>
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>
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>
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>
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>
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>
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:
CLI / orchestration changes
E2E test coverage
Added a parallel Node 24 / webpack5 e2e suite alongside the existing Node 22/webpack4 one, without altering the latter's behavior:
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
Contributing to Twilio