Skip to content

Instantly share code, notes, and snippets.

@tiberiuichim
Created August 31, 2026 07:21
Show Gist options
  • Select an option

  • Save tiberiuichim/ddc8d3ca8d153389066b1dc9ef5c052b to your computer and use it in GitHub Desktop.

Select an option

Save tiberiuichim/ddc8d3ca8d153389066b1dc9ef5c052b to your computer and use it in GitHub Desktop.
Playbook: working on two Volto addon packages at once (inter-package dependencies + standalone CI)

Working on Two Volto Addon Packages at Once (inter-package dependencies)

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).

The setup that causes pain

  • Every addon under frontend/src/addons/ is its own git repo (submodule); PRs target develop.
  • 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-yarn image → addon copied to /app/src/addons/<name>/setupAddon adds it as a yarn workspace to a bare Volto app → yarn install → lint/prettier/test. There, the sibling addon is only what yarn can 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.

Checklist (do all of these in the CONSUMING repo)

1. Declare the dependency in package.json — both arrays

"addons":       [ ..., "@eeacms/volto-eea-chatbot" ],
"dependencies": { ..., "@eeacms/volto-eea-chatbot": "^4.0.0" }
  • The addons array is what makes @plone/registry's AddonRegistry register the package → the addon's .eslintrc.js builds an eslint alias entry for it → lint can resolve the module.
  • dependencies is what actually makes yarn install put it in node_modules (needed for alias resolution, jest, and the real build).
  • Both are required; one alone is not enough.

2. Pin a version that EXISTS on the registry for CI

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 with npm pack @scope/pkg@<ver> + tar -tzf that the deep path you import (and its index file) 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.
  • 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.

3. Make sure the main frontend declares the sibling addon too

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.

4. Handle the eslint TS-extension gotcha (verify, don't assume)

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.js now has extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'] (commit a007606c). 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 .js file), and import the bare package name.

Audit done 2026-09 (all of frontend/src/addons/):

  • Every addon .eslintrc.js uses 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-level frontend/.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) and volto-cca-policy (this fix).
  • Among installed @eeacms/* packages only volto-eea-chatbot contains TypeScript, so only its consumers can hit this. Consumers:
    • volto-cca-policyChatBlock/chat (an index.ts) — was broken, now fixed.
    • volto-searchlibChatBlock/hocs/withOnyxData (.jsx, resolves fine today) — but latent: if that file is migrated to .ts in the chatbot repo, searchlib's standalone CI breaks identically. Cheap insurance: apply the same one-line extension fix to volto-searchlib's .eslintrc.js next time that repo is touched.

5. Decide the merge order explicitly and say it in both PR bodies

Canonical order: sibling (upstream) PR first, then consuming PR.

  1. Sibling feature branch → merged to its develop → released to npm (e.g. 4.0.1/4.1.0).
  2. 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).
  3. 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.

6. Verify against a real CI replica, not the local monorepo

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.)

Quick diagnosis table (when it's red)

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

House rules

  • 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.json even while developing; don't use file: or git URLs in published addons.
  • Update CHANGELOG.md in the consuming repo when bumping the dependency after the sibling release.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment