Operating notes for coding agents working in contentful-cli. Read this before
changing anything. For how the pieces fit together see
ARCHITECTURE.md; for commit conventions and the human
contribution flow see CONTRIBUTING.md.
The contentful command line tool, published to npm as contentful-cli and
also shipped as standalone binaries for macOS, Linux and Windows. It is a
yargs CLI written in a mix of TypeScript and JavaScript
that talks to the Contentful Management API through contentful-management.
Owner: @contentful/group-applied-ai-solutions (see .github/CODEOWNERS and
catalog-info.yaml).
npm install.npmrc sets ignore-scripts=true, so lifecycle scripts do not run on
install. If you need them (husky hooks, the pkg build's esbuild), run:
npx allow-scriptsThe allowlist lives under the lavamoat.allowScripts key in package.json.
Adding a dependency that needs an install script means adding it there too.
See docs/ADRs/2026-08-25-disable-npm-install-scripts-by-default.md.
Node: >=22 per package.json engines; .nvmrc pins 24, which is also the
version CI uses.
| Task | Command |
|---|---|
Type-check / compile to dist/ |
npm run tsc |
| Lint | npm run lint (eslint over bin lib test) |
| Format | npm run prettier:write |
| Unit tests | npm run test:unit |
| Unit tests, single path | npx jest test/unit/cmds/space_cmds --verbose |
Unit tests with coverage (the default npm test) |
npm run test:coverage |
| Standalone binaries | npm run build:standalone |
| Binaries + zipped release artifacts | npm run build:package |
| Integration tests | npm run test:integration |
| E2E tests (needs a built binary) | npm run test:e2e |
nyc enforces 80% line coverage (package.json → nyc.check-coverage), so
npm test fails if your change drops coverage below that.
- Unit tests (
test/unit/**) are the only tier you can rely on without Contentful credentials, and even thereCONTRIBUTING.mdnotes some currently read the integration env vars. - Integration tests (
test/integration/**) run against thetalkbackproxy intest/proxy.js, replaying the HTTP tapes inrecordings/. Recording new tapes requiresCONTENTFUL_INTEGRATION_TEST_CMA_TOKENandCLI_E2E_ORG_IDfor the Ecosystem integration-test org. - E2E tests (
test/e2e/**) shell out to the packaged binary inbuild/, sonpm run build:standalonemust have run first.
If you do not have those secrets, say so rather than reporting a test tier as
passing. Never commit a token into recordings/, a snapshot, or a fixture —
test/proxy.js strips authorization and x-contentful-user-agent headers
from tapes, and that guarantee only holds if you leave it alone.
Commands are discovered by yargs commandDir, so file placement is the
routing — there is no central registry to update.
- A top-level command is a module in
lib/cmds/exportingcommand,descand abuilderthat calls.commandDir('<name>_cmds')(seelib/cmds/space.js). - A subcommand is a file in the matching
lib/cmds/<name>_cmds/directory exportingcommand,desc, optionalaliases,builder, andhandler. Wrap the handler inhandleAsyncErrorfromlib/utils/async.tsso failures surface as CLI errors rather than unhandled rejections —lib/cmds/space_cmds/use.tsis the reference implementation. - Build API clients with
createManagementClient/createPlainClientfromlib/utils/contentful-clients.js. Do not callcontentful-management'screateClientdirectly; the wrappers apply proxy config,host,insecureand thecontentful.cli/<version>application header. - If the command works without a login or without an active space, add its
full command string (e.g.
'space use') tonoAuthNeeded/noSpaceIdNeededinlib/config.js. Otherwise theassertContextmiddleware will reject it. - Add or update the matching page under
docs/<command>/—docs/README.mdindexes the command tree and thedocsdirectory is shipped in the npm package (package.json→files).
New code should be TypeScript. The repo is mid-migration (allowJs: true in
tsconfig.json, roughly half the files still .js); do not bulk-convert
existing JavaScript as a side effect of an unrelated change.
- Commit messages follow the Angular convention and drive semantic-release.
featandfixonmaincut a release; usedocs,chore,test,refactororstylewhen you do not want one.build(deps)is configured to release a patch, so it is not a safe "no release" type here. - Prettier config is
.prettierrc; eslint extendseslint:recommended,prettierandplugin:@typescript-eslint/recommended. - User-facing output goes through
lib/utils/log.jsand the helpers inlib/utils/styles.js/lib/utils/emojis.js, not bareconsole.log.
- Do not weaken
.npmrcor.gitignore. - Do not commit
dist/,build/oroutput/— all three are gitignored build output. - Do not hand-edit
package.json'sversion; it is0.0.0-determined-by-semantic-releaseand set at publish time. - Do not add reviewers or merge on your own behalf.