We appreciate any community contributions to this project, whether in the form of issues or pull requests.
This document explains how to set up the repository, how packages and reference implementations fit together, which validation to run for common kinds of changes, and which local hooks and CI behaviors to expect.
Many subtrees also contain local AGENTS.md files. They are written for agent tooling, but they
also serve as concise local runbooks for humans. When you begin working in a package,
implementation, or lib/ workspace, read the nearest AGENTS.md for subtree-specific commands and
gotchas.
Working on your first Pull Request? You can learn how from this extensive list of resources for people who are new to contributing to Open Source.
The following software is required or strongly recommended for day-to-day development:
.nvmrc when possible; the minimum supported
version is 20.19.0)package.json)jq for the local pre-push hookimplementations/web-sdkimplementations/react-native-sdkBrowser and implementation-specific E2E flows also require Playwright browser binaries. The
targeted pnpm setup:e2e:<implementation> wrappers install them for you.
After cloning the repository:
nvm use
pnpm install
pnpm version:node
pnpm version:pnpm
pnpm install also installs the local Husky hooks used during commit and push.
| Path | Purpose |
|---|---|
lib/ |
Internal shared tooling and mock services, such as build-tools and mocks |
packages/ |
Workspace packages, including the published SDKs and framework layers |
implementations/ |
Reference applications used for integration testing, local demos, and E2E coverage |
pkgs/ |
Generated tarballs created by pnpm build:pkgs; implementations install from these |
docs/ |
Generated TypeDoc output |
dist/, coverage/ |
Generated build and test artifacts inside individual workspaces |
The most important repository-specific mechanic is this:
pnpm build:pkgs and
reinstall that implementation before trusting local results.pnpm setup:e2e:<implementation> wrappers do this refresh for you as part of E2E
setup.The root package.json contains more scripts than are listed below. These are the
ones most contributors need regularly.
| Command | When to use it |
|---|---|
pnpm lint |
Lint lib/ and packages/ workspaces |
pnpm implementation:lint |
Lint implementations/ |
pnpm implementation:install |
Refresh all implementations after rebuilding package tarballs |
pnpm typecheck |
Type-check all workspaces |
pnpm implementation:typecheck |
Type-check all implementations |
pnpm test:unit |
Run unit tests across workspace packages |
pnpm build |
Build all @contentful/* packages |
pnpm build:pkgs |
Build packages and create implementation-consumable tarballs in pkgs/ |
pnpm size:check |
Validate bundle-size budgets for all built packages |
pnpm size:report |
Report bundle sizes without failing on budgets |
pnpm docs:generate |
Generate TypeDoc output |
pnpm format:check |
Check repository formatting |
pnpm setup:e2e:<implementation> |
Prepare one implementation for E2E, including package refresh and local setup |
pnpm test:e2e:<implementation> |
Run one implementation's full E2E flow |
pnpm implementation:run -- <implementation> ... |
Run a specific helper action or package-local script inside one implementation |
pnpm playwright:install |
Install Playwright browsers across implementations |
pnpm playwright:install-deps |
Install Playwright system dependencies on Linux |
pnpm serve:mocks |
Run the shared mock services used by local flows |
Most workspaces also define targeted local scripts such as dev, build, test:unit, and
size:check. Prefer targeted validation in the affected package or implementation instead of
running the whole repository when the change is narrow.
Before running pnpm implementation:lint across the repository, run pnpm implementation:install
so the reference implementations have current local package tarballs installed.
lib/ AGENTS.md.Example:
pnpm lint
pnpm --filter @contentful/optimization-web typecheck
pnpm --filter @contentful/optimization-web test:unit
pnpm --filter @contentful/optimization-web build
pnpm --filter @contentful/optimization-web size:check
pnpm build:pkgs
pnpm implementation:run -- web-sdk implementation:install
If your next step is E2E rather than a narrow manual reinstall, prefer the targeted wrapper:
pnpm setup:e2e:web-sdk
pnpm test:e2e:web-sdk
AGENTS.md.pnpm build:pkgs and reinstall the
implementation first.Example:
pnpm implementation:run -- web-sdk_react typecheck
pnpm implementation:run -- web-sdk_react build
pnpm implementation:run -- web-sdk_react implementation:test:e2e:run
.env from the implementation's .env.example if the implementation expects one
and you do not already have it.Example:
cp implementations/web-sdk/.env.example implementations/web-sdk/.env
pnpm setup:e2e:web-sdk
pnpm test:e2e:web-sdk
Environment notes:
web-sdk requires Docker because the app is served via nginx.react-native-sdk requires an Android emulator for Detox flows.implementation:run is the shared helper used by the implementation scripts listed above.
Run a helper action for all implementations:
pnpm implementation:run -- --all -- <action> [args...]
Run a helper action for one implementation:
pnpm implementation:run -- <implementation> <action> [args...]
<implementation> is a folder name under implementations/ (for example: web-sdk,
web-sdk_react, node-sdk, node-sdk+web-sdk, react-native-sdk)<action> can be one of these helper actions:implementation:installimplementation:build:runimplementation:test:unit:runimplementation:playwright:installimplementation:playwright:install-depsimplementation:setup:e2eimplementation:test:e2e:run<action> can also be any implementation-local script name (for example: serve, serve:stop,
test:e2e:ui)[args...] are forwarded to the target script/action (when supported by that action/script)Examples:
# Install dependencies for every implementation
pnpm implementation:run -- --all -- implementation:install
# Run the implementation-level E2E command for all implementations
pnpm implementation:run -- --all -- implementation:test:e2e:run
# Run one implementation's local script
pnpm implementation:run -- web-sdk test:e2e:ui
# Pass arguments through to the underlying E2E script
pnpm implementation:run -- node-sdk implementation:test:e2e:run -- --grep "homepage"
Prefer the root wrapper scripts when they already match what you want to do. For example:
pnpm implementation:installpnpm setup:e2e:<implementation>pnpm test:e2e:<implementation>Use the smallest meaningful validation set for the change.
| Change type | Usually run | Notes |
|---|---|---|
| Docs-only or markdown-only | pnpm format:check |
Also run pnpm docs:generate if public API docs or linked markdown changed |
Package or lib/ TypeScript change |
Targeted lint, typecheck, test:unit, and build |
Prefer pnpm --filter <workspace> ... when the change is narrow |
| Built package runtime, export, dependency, or bundle change | Targeted size:check or root pnpm size:check |
Bundle-size checks are currently a contributor-run validation, not a dedicated CI job |
| Package change used by an implementation | pnpm build:pkgs, then reinstall the affected implementation |
Use pnpm setup:e2e:<implementation> if E2E is your next step |
| Implementation code change | pnpm implementation:lint, targeted implementation typecheck, and implementation-local tests |
Some implementations have no meaningful unit tests; E2E matters more there |
| Shared build or packaging change | Broaden validation to downstream packages and at least one affected implementation | Examples: lib/build-tools, package exports, tarball/install flow |
| User-visible integration or runtime behavior change | Targeted implementation E2E | Choose the implementation that exercises the changed surface |
When in doubt, start targeted and broaden only if the change crosses package or implementation boundaries.
This project uses ESLint and Prettier to enforce coding and formatting conventions. It may be useful to enable related editor plugins to have a smoother experience when working on Optimization SDKs.
Please review the following files to familiarize yourself with current configurations:
Local Git hooks installed by Husky:
pre-commit runs lint-staged on staged files.prepare-commit-msg opens a Commitizen prompt when you do not supply a commit message and you are
not amending HEAD.pre-push runs targeted type checks and unit tests for changed workspaces and implementations.Practical implications:
test:unit work to run on push.Code is documented using TSDoc, and reference documentation is generated using TypeDoc and published automatically with each new version.
documentation/, while docs/ remains generated outputpnpm docs:generate generates documentation from TSDoc code comments, package README files, and
markdown files under documentation/pnpm docs:watch watches for file updates and rebuilds documentation output; useful while writing
and updating documentationWhen changing public SDK behavior in this pre-release alpha period, update the same pull request to keep these artifacts aligned:
documentation/ contains source markdown that TypeDoc publishes. docs/ is generated output. Do
not hand-edit generated TypeDoc output.
pnpm build:pkgs, then
reinstall the affected implementation with
pnpm implementation:run -- <implementation> implementation:install.pnpm playwright:install, or use the targeted
pnpm setup:e2e:<implementation> wrapper.pnpm playwright:install-deps.implementations/web-sdk fails to serve locally: confirm Docker is running..env with
its checked-in .env.example.3000, 8000, or 8081 is already in use: stop only the relevant local
process or implementation serve flow rather than using broad PM2 cleanup.Main Pipeline runs implementation E2E jobs when path filters request them for both:
mainThis is an intentional CI policy:
.env.example values to keep runs
deterministic and stable.The path filters do not watch only implementation directories. Shared package and root changes can also trigger implementation E2E. At a high level:
| E2E job | Also watches shared surfaces |
|---|---|
e2e_node_ssr_only |
lib/**, packages/node/node-sdk/**, universal packages, root package/workflow files |
e2e_node_ssr_web_vanilla |
lib/**, packages/node/node-sdk/**, packages/web/web-sdk/**, packages/web/preview-panel/**, shared root files |
e2e_web |
lib/**, packages/web/web-sdk/**, packages/web/preview-panel/**, universal packages, shared root files |
e2e_web_react |
lib/**, packages/web/web-sdk/**, packages/web/preview-panel/**, universal packages, shared root files |
e2e_react_native_android |
lib/**, packages/react-native-sdk/**, universal packages, shared root files |
See .github/workflows/main-pipeline.yaml for the exact
authoritative filter list.
Skipping an implementation E2E job because its filter did not match is expected behavior, not a CI coverage defect.
If a change should trigger an implementation E2E job but does not match the current filters, update
the path filters in .github/workflows/main-pipeline.yaml in the same pull request.
E2E setup does not depend on repository secrets. Each implementation creates .env from its own
checked-in .env.example file in CI, which keeps fork PR behavior aligned with internal PRs.
Run license-checker locally:
pnpx license-checker --summary
pnpx license-checker > licenses.txt
If the license for a package merely has a spelling or formatting difference from an existing entry
in the license-check GitHub workflow allow list, update the list and submit the change via pull
request. Otherwise, create an issue to receive further guidance from the maintainers.