feat(CMA): Add entry, asset, and content-type CRUD commands#3272
feat(CMA): Add entry, asset, and content-type CRUD commands#3272Ivo Toby (ivo-toby) wants to merge 11 commits into
Conversation
…d output Implement complete CLI command surface for entries (list, get, create, update, publish, unpublish, archive, unarchive, delete), assets (list, get, upload, update, publish, unpublish, delete), and content-types (get, create, update, publish, unpublish, delete). Built on a declarative command-factory that wires up standard options (--json, --quiet, --agent-mode, --dry-run), runtime context fallback for auth/space/environment, and TOON-format output for agent consumption. Includes unit and integration tests for all commands and utilities. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add spy on process.exit in beforeEach and restore it in afterEach to mock process termination calls during upload command tests
Add firstLocaleValue to jest mock for output utility across asset command tests to prevent mock-related errors. Replace inline exit spy creation with shared exitSpy variable and simplify test assertions by removing try-catch blocks around process.exit calls.
Wiz Scan Summary
To detect these findings earlier in the dev lifecycle, try using Wiz Code VS Code Extension. |
… client
Replace createManagementClient (legacy nested client) with
createPlainClient in command-factory and all command handlers.
This eliminates the deprecation warning from contentful-management v12.
API changes:
- environment.getEntry(id) → client.entry.get({entryId: id})
- environment.getAsset(id) → client.asset.get({assetId: id})
- environment.getContentType(id) → client.contentType.get({contentTypeId: id})
- entity.publish/unpublish/delete/update() → client.<type>.<method>({id}, data)
All 329 command tests and 38 command-factory tests updated and passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5258d3dac1
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "Codex (@codex) review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "Codex (@codex) address that feedback".
The old list.js was still using createManagementClient (nested/legacy client), causing the deprecation warning. Replaced with a TypeScript version using createPlainClient and the standard createCommand factory. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Makes commands more discoverable by showing concrete usage examples in help text, useful for both humans and LLM-driven workflows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Address PR review P1 feedback: 1. Remove `default: 'master'` from --environment-id yargs option. The middleware already resolves active environment from user config and falls back to master — the yargs default made omission indistinguishable from explicit use, overriding the configured active environment. 2. Fetch the space's default locale dynamically in asset upload instead of hardcoding en-US. Spaces without en-US would fail on upload when --locale was omitted. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
| } | ||
| }, | ||
| handler: async (client, argv) => { | ||
| return client.contentType.unpublish({contentTypeId: argv.id}) |
There was a problem hiding this comment.
today I learned that content types can be unpublished :)
There was a problem hiding this comment.
We should also add these new commands to the ./docs/README.md https://github.com/contentful/contentful-cli/blob/main/docs/README.md
There was a problem hiding this comment.
As well as existing ./docs/content-type
| // eslint-disable-next-line @typescript-eslint/no-var-requires | ||
| const { createPlainClient } = require('./contentful-clients') as { | ||
| createPlainClient: ( | ||
| params: { |
There was a problem hiding this comment.
Could we use type ClientOptions to reduce risk of drift between CMA and CLI client initialization?
| /** Command description shown in help */ | ||
| desc: string | ||
| /** SDK tracking feature string, e.g. 'entry-list' */ | ||
| feature: string |
There was a problem hiding this comment.
Feature, another today I learned. I'm not sure that's going to any datadog dashboard that anyone is reading/depending upon, but good to know about.
| flags: OutputFlags, | ||
| options: OutputOptions<TData> | ||
| ): void { | ||
| if (flags.json) { |
There was a problem hiding this comment.
Any risk of circular json references? Might be worth it to wrap in a try/catch
There was a problem hiding this comment.
Is this supposed to be in our codebase? This seems more like it should be a dependency.
| // Core encode generators | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| function* encodeJsonValue( |
There was a problem hiding this comment.
Frantically searching MDN for what function\* does. Flashback to C++ pointers.
| export const EXIT_CLIENT_ERROR = 1 | ||
| export const EXIT_SERVER_ERROR = 2 | ||
|
|
||
| interface ErrorWithStatus { |
There was a problem hiding this comment.
Can we add message/details? Need to check if CF-stacks or some other shared types already solved this problem.
There was a problem hiding this comment.
Would love it if these types could be imported from CF-stacks or a similar source of truth. Even the CMA, which is already a dep.
| import { confirmation } from './actions' | ||
| import { warning } from './log' | ||
|
|
||
| interface ErrorWithName { |
There was a problem hiding this comment.
question/optional: Add to other standarized errors in exit-codes.ts
|
|
||
| for (const item of items) { | ||
| const revoked = item?.revokedAt | ||
| ; (revoked) // no-op to satisfy linter if unused |
There was a problem hiding this comment.
yikes.
| const entry = await client.entry.get({ entryId: id }) | ||
| return client.entry.publish({ entryId: id }, entry) | ||
| }, | ||
| dryRunHandler: async (client, argv) => { |
There was a problem hiding this comment.
I think this would require some more significant backend changes, but it would REALLY take the cli to the next level if we could run the content type field validations in a dry run before actually attempting to publish.
A LOT of the time I see customers that are attempting to publish an entry with 422 field validation errors and the script/cmd fails silently 👎
| handler: async (client, argv) => { | ||
| const id = validateId(argv.id, 'Entry ID') | ||
| const entry = await client.entry.get({ entryId: id }) | ||
| return client.entry.publish({ entryId: id }, entry) |
There was a problem hiding this comment.
Today the CMA does not check for unpublished references before publishing, but it'd be sweet if it did
ethan ozelius (ethan-ozelius-contentful)
left a comment
There was a problem hiding this comment.
Looks really good, left a TON of comments, sorry for the wall of text. Thanks for fixing the countless linting issues.
|
One last, last thing, that should probably be handled in a follow up, but I don't want to forget it: locale based publishing should maybe be added? I don't think it's available to all customers, so that would complicate things, but let's discuss. |

Summary
Adds a complete CLI command surface for managing entries, assets, and content types via the Contentful Management API. Built on a new declarative
createCommandfactory that standardizes option handling, output formatting, and error behavior across all commands.New commands:
contentful entry— get, list, create, update, delete, publish, unpublish, archive, unarchivecontentful asset— get, list, upload, update, delete, publish, unpublishcontentful content-type— get, create, update, delete, publish, unpublish (replaces old JS-basedget)New infrastructure:
lib/utils/command-factory.ts— Declarative command builder wiring standard options (--space-id,--environment-id,--management-token,--json,--quiet,--agent-mode,--dry-run,--yes) and error handlinglib/utils/output.ts— Unified output dispatch (JSON, table, key-value, quiet/ID-only, TOON for agents)lib/utils/exit-codes.ts— Structured exit codes (1 = client error, 2 = server error)lib/utils/validators.ts— Input validation helpers (ID format, JSON field parsing)lib/utils/toon-encoder.ts+lib/utils/toon.ts— TOON format encoder for agent-mode outputKey design decisions
--dry-runto preview operations without side effects.--json(raw API response),--quiet(IDs only for piping),--agent-mode(TOON format for LLM agent consumption).--versionto prevent accidental overwrites.--yesis passed.Changes
lib/cmds/{asset,entry,content-type}_cmds/lib/utils/lib/cmds/content-type_cmds/get.jswith TypeScript versionTest plan
npm test)npm run tsc)npm run test:integration)contentful entry list --space-id <id> --jsoncontentful asset upload --file ./test.png --title "Test" --dry-run--quietpiping works:contentful entry list --content-type page -q | head -5