Published as a gist: https://gist.github.com/tiberiuichim/ddc8d3ca8d153389066b1dc9ef5c052b
When a feature spans two addon repos (e.g. a core addon + a presentation addon) and one starts importing the other's code, CI and versioning get tricky because each addon's Jenkins build is standalone — it cannot see the sibling feature branch. This doc is the playbook distilled from the chatbot "catalogue" variation work (see
HANDOVER-CHATBOT-CATALOGUE.md, gotcha #11).
- Every addon under
frontend/src/addons/is its own git repo (submodule); PRs targetdevelop. - Locally (full cca frontend) everything resolves because the sibling addon is
checked out in
src/addons/and the registry/razzle aliases point at its working tree — feature branches included. - In CI, Jenkins builds each addon standalone:
plone/frontend-builder:18-yarnimage → addon copied to/app/src/addons/<name>→/setupAddonadds it as a yarn workspace to a bare Volto app →yarn install→ lint/prettier/test. There, the sibling addon is only whatyarncan install from the published npm registry — never your unmerged feature branch.
Consequence: "works locally, fails in CI" is the default outcome for a new cross-addon import. Budget for it.
"addons": [ ..., "@eeacms/volto-eea-chatbot" ],
"dependencies": { ..., "@eeacms/volto-eea-chatbot": "^4.0.0" }- The
addonsarray is what makes@plone/registry'sAddonRegistryregister the package → the addon's.eslintrc.jsbuilds an eslint alias entry for it → lint can resolve the module. dependenciesis what actually makesyarn installput it innode_modules(needed for alias resolution, jest, and the real build).- Both are required; one alone is not enough.
CI cannot install your sibling feature branch. So:
- Pin to the latest published version (e.g.
^4.0.0). CI lint only needs the module path to exist — verify withnpm pack @scope/pkg@<ver>+tar -tzfthat the deep path you import (and itsindexfile) is in the tarball. - The new APIs you use (from the sibling feature branch) will NOT be in
that published version. That is fine as long as:
- your CI stages only reference them in a resolvable way (imports resolve
at module level; missing named exports are not checked by
import/no-unresolved), and - you have no CI unit tests that actually execute the new-API code paths. Jest tests that import the new symbols from the published package will fail in CI.
- your CI stages only reference them in a resolvable way (imports resolve
at module level; missing named exports are not checked by
- Runtime in the real cca frontend uses the git submodule (via
mrs.developer.json), so it sees the feature branch locally — this is what lets you develop/test in the browser before either PR merges.
frontend/package.json (addons + dependencies) and
frontend/mrs.developer.json must list the sibling addon so the local
frontend installs it from git. If the feature branch in the submodule is the
one you're testing against, that's what your browser sees.
The addon .eslintrc.js (Volto template) alias resolver historically only
tried ['.js', '.jsx', '.json']. Any deep import into a TypeScript addon
that lands on a directory index.ts (e.g.
@eeacms/volto-eea-chatbot/ChatBlock/chat → .../src/ChatBlock/chat/index.ts)
fails import/no-unresolved in CI only.
volto-cca-policy/.eslintrc.jsnow hasextensions: ['.js', '.jsx', '.json', '.ts', '.tsx'](commita007606c). This is a template file — if it ever gets regenerated, re-check this line.- If you hit this in another addon, same one-line fix in its
.eslintrc.js. - Alternative to dodge it entirely: make the upstream addon re-export the
needed symbols from its package root (
src/index.js, a.jsfile), and import the bare package name.
Audit done 2026-09 (all of frontend/src/addons/):
- Every addon
.eslintrc.jsuses the stock Volto addon template (['.js','.jsx','.json']) — the cca-policy one was NOT a stale custom fork. The addon template itself lags behind the project-levelfrontend/.eslintrc.js, which already ships['.js','.jsx','.ts','.tsx','.json']. - Only two addons diverge:
volto-eea-chatbot(has.ts/.tsx, plus its own'no-console': 'off'relaxation — chatbot-specific, don't copy that) andvolto-cca-policy(this fix). - Among installed
@eeacms/*packages onlyvolto-eea-chatbotcontains TypeScript, so only its consumers can hit this. Consumers:volto-cca-policy→ChatBlock/chat(anindex.ts) — was broken, now fixed.volto-searchlib→ChatBlock/hocs/withOnyxData(.jsx, resolves fine today) — but latent: if that file is migrated to.tsin the chatbot repo, searchlib's standalone CI breaks identically. Cheap insurance: apply the same one-line extension fix tovolto-searchlib's.eslintrc.jsnext time that repo is touched.
Canonical order: sibling (upstream) PR first, then consuming PR.
- Sibling feature branch → merged to its
develop→ released to npm (e.g.4.0.1/4.1.0). - Update the consuming repo's dependency to a range that includes that
release (with
^/*you may not need a change — verify the published version actually contains the APIs you use, since your code will start executing against it in CI tests and in production). - Merge the consuming PR.
Until step 1, the consuming branch is safe to keep open; its CI runs against the old published version (lint-only, per step 2). Never leave consumer-side tests that depend on unreleased sibling APIs — they will fail the moment your branch CI reruns against the old package.
The local full-frontend setup masks both failures (submodule aliases +
webpack handling .ts). Replicate the Jenkins build:
# from the consuming addon directory
BRANCH=feature-xyz
rm -rf /tmp/ci-repro && mkdir /tmp/ci-repro
git archive origin/$BRANCH | tar -x -C /tmp/ci-repro
cd /tmp/ci-repro
docker build --pull \
--build-arg="VOLTO_VERSION=18-yarn" \
--build-arg="ADDON_NAME=@eeacms/volto-cca-policy" \
--build-arg="ADDON_PATH=volto-cca-policy" \
. -t ci-repro
docker run --rm --entrypoint=make \
--workdir=/app/src/addons/volto-cca-policy ci-repro lint
# also: stylelint / prettier / test-ci, to mirror the remaining Jenkins stages(--pull matters: it refreshes the base image like Jenkins does.)
| Symptom in CI | Cause | Fix |
|---|---|---|
import/no-unresolved for @scope/sibling/... |
package not in dependencies/addons → not installed, no alias |
step 1 |
Same, but package IS installed (check node_modules in the image) |
alias resolver doesn't try .ts/.tsx → dir index.ts |
step 4 |
| Import resolves, but named export is missing at test runtime | CI installed the published version, which predates your sibling PR | step 2 / 5 — wait for the sibling release, or drop the test |
| Works in your browser, breaks in the real built frontend | main frontend/ package.json/mrs.developer.json missing the sibling |
step 3 |
- Commits in each addon happen inside the submodule (cd into it), on
hyphenated feature branches, PR →
develop. - Add the version-pin decision + merge order to both PR bodies and the handover doc; the next person shouldn't have to rediscover it.
- Keep the sibling dependency at a real semver range in
package.jsoneven while developing; don't usefile:or git URLs in published addons. - Update
CHANGELOG.mdin the consuming repo when bumping the dependency after the sibling release.