Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

commands

A Drupal module providing standalone Symfony Console commands for content-type authoring — creating content types, adding fields, attaching workflows, granting permissions, and generating bundle classes. Every command saves directly to active config via the entity API; nothing reads or writes YAML files directly. Run drush config:export when you're done to persist changes to disk.

Each command works two ways:

  • Interactively — run it with no arguments (or with some missing) and it prompts for whatever's needed.
  • Non-interactively — pass every argument/option plus --no-interaction, suitable for scripting or for an agent (see Using with Claude Code below).

Commands run through dr, Drupal core's own native CLI, introduced in Drupal 11.4 — Drush is not required to run them (see Development notes).

Requirements

  • Drupal ^11.4 — needed for the native dr CLI and its #[AsCommand] auto-discovery; this module doesn't work on earlier core versions
  • drupal:node (declared in commands.info.yml)
  • Optional, only if you use the matching command: Drupal core's Workflows module (content-type:workflow:add), and the contrib Scheduled Transitions module if you want the 3 scheduled-transition permissions that content-type:permissions:grant otherwise skips (see below)
  • The module still needs to be enabled for dr to discover its commands — do that however your site normally enables modules (drush en commands, the UI, drush/drush isn't otherwise required for anything in this repo)

Installation

composer require previousnext/commands   # or however this repo is required
drush en commands                        # or enable it via the UI

Commands

Command Alias Purpose
content-type:create ctc Create a new content type
content-type:field:add ctfa Add a field to a content type
content-type:workflow:add ctwa Attach a content type to a workflow
content-type:permissions:grant ctpg Grant standard node permissions for a content type to roles
content-type:bundle-class:generate ctbcg Generate a PHP bundle class (and optional enum case) for a content type
content-type:list ctl List reference data (fields, node types, roles, workflows, modules, enums, entity types/bundles) as JSON

Run dr list on your own site to confirm what's registered, and dr help <command> for full option details.

content-type:create

Creates a node type and saves it to active config.

dr content-type:create research "Research" --description="Research content"
dr content-type:create research "Research" --help-text="Shown on the node form" --no-revision

Arguments: machine_name (required), label (required). Options: --description, --help-text, --no-revision (disables new revisions on save; revisions are on by default).

content-type:field:add

Adds a field to a content type — either reusing an existing field storage from another bundle, or creating a brand-new one. Loops to add multiple fields in one interactive run; non-interactively it adds exactly one per invocation.

# Reuse an existing field storage on another bundle.
dr content-type:field:add research introduction --reuse

# Create a new field.
dr content-type:field:add research pub_date --type=datetime --label="Publication date"
dr content-type:field:add research authors --type=entity_reference --cardinality=-1 \
  --_type_settings='{"storage_settings":{"target_type":"user"},"field_settings":{"handler":"default:user","handler_settings":{"target_bundles":null}}}'

Arguments: bundle, field_name (both optional — prompted for if missing). Options: --reuse, --type (field type plugin ID, required unless --reuse), --label, --description, --required, --cardinality (-1 for unlimited, default 1), --_type_settings (JSON-encoded {"storage_settings": {...}, "field_settings": {...}} — the type-specific settings the interactive prompts would otherwise collect).

content-type:workflow:add

Attaches a content type to a workflow by updating the workflow config entity directly.

dr content-type:workflow:add research csnsw_workflow

Arguments: bundle, workflow_id (both optional). Fails cleanly with an "enable the Workflows module" error if the workflow entity type doesn't exist on the site, rather than crashing.

content-type:permissions:grant

Grants the standard set of node content permissions for a bundle to one or more roles:

  • create/edit any/delete any {bundle} content
  • delete/revert/view {bundle} revisions
  • add/reschedule/view scheduled transitions node {bundle}
dr content-type:permissions:grant research --roles=editor,content_admin

Arguments: bundle (optional). Options: --roles (required, comma-separated role IDs).

The 3 scheduled-transitions permissions only exist if the Scheduled Transitions contrib module is installed and the bundle has been explicitly opted into it via scheduled_transitions.settings — being on a Content Moderation workflow isn't enough on its own. The command checks what actually exists on the site before granting, warns about anything it skips, and reports the real number granted — it never reports success for a permission it couldn't actually assign.

content-type:bundle-class:generate

Generates a #[Bundle]-attributed PHP class for a content type (using Drupal\bca\Attribute\Bundle from the contrib BCA module — the generated class won't compile without it) and registers it on the node_type entity. Optionally adds a case to an existing backed-string enum, or scaffolds a new one.

dr content-type:bundle-class:generate research --module=csnsw_node --class=Research

# With an enum case:
dr content-type:bundle-class:generate research_paper \
  --module=csnsw_node --class=ResearchPaper \
  --enum='Drupal\csnsw_node\CsnswNodeTypes' --enum-case=RESEARCH_PAPER

Arguments: bundle (optional). Options: --module, --class, --enum (an existing enum's FQN, new:{module}:{EnumClassName} to scaffold one, or omit/none to skip), --enum-case (UPPER_SNAKE_CASE, required unless the enum is skipped).

Writes {module}/src/Entity/Node/{ClassName}.php and either updates or creates the enum file — these are real PHP source file writes, not config entities, so review the diff before committing.

content-type:list

Prints JSON reference data for the other commands — the thing you'd otherwise need ad hoc PHP one-liners for.

dr content-type:list field-storages
dr content-type:list node-types
dr content-type:list roles
dr content-type:list workflows
dr content-type:list modules
dr content-type:list enums
dr content-type:list entity-types
dr content-type:list bundles --entity-type=taxonomy_term

Argument: subject — one of field-storages, node-types, roles, workflows, modules, enums, entity-types, bundles. bundles requires --entity-type. workflows returns {} if the Workflows module isn't enabled, rather than erroring.

Using dr, Drupal core's native CLI

dr ships with Drupal core as of 11.4 — no separate install step, and no Drush required. It boots Drupal, scans src/Command/ in every enabled module for #[AsCommand]-attributed classes, autowires their constructors from the service container, and registers each one, all without any services.yml entries.

dr content-type:create research "Research"
dr content-type:list roles

Note that generic Drupal operations that aren't part of this module — config:export, cache:rebuild, and so on — are unaffected by any of this and still go through Drush (or whatever else your site uses for them); dr only knows about commands, not Drush's broader command set.

Using with Claude Code

There's a Claude Code skill, create-content-type, that drives all of the above end-to-end: it asks you the same questions an interactive terminal run would (content type basics, which fields to reuse, new fields with type-specific settings, workflow, permissions, optional bundle class), using content-type:list to fetch live site data instead of guessing, and runs every command non-interactively once it has your answers. Say something like "create a new content type", "add fields to a content type", or "scaffold a content type" to trigger it, or invoke it directly as /create-content-type.

The skill lives outside this repo (in a shared skills directory) and expects this module to be enabled and Docker Compose running — see the skill file itself for the full prerequisites and step-by-step workflow.

Using with OpenCode

opencode/tools/content-type.ts wraps create, field_add, workflow_add, permissions_grant, and config_export as typed OpenCode tools (it doesn't yet cover content-type:bundle-class:generate or content-type:list — use dr directly for those). Symlink or copy it to .opencode/tools/content-type.ts in your project to pick it up.

Development notes

  • Every command is a plain Symfony Console #[AsCommand] class living under src/Command/, in the Drupal\commands\Command namespace. That namespace/directory pair is exactly what Drupal core's dr CLI scans for in enabled modules — no services.yml entry, no manual console.command tag. Constructors are autowired straight from the container, same as any other Drupal service.
  • commands.services.yml only declares the plain helper services below — none of the command classes themselves are listed there.
  • Shared logic lives in src/Service/: FieldStorageReader (existing node fields/types), FieldTypeSettingsCollector (interactive field-type settings), ModuleDiscoveryService (custom module discovery), FileGeneratorService (Twig rendering via Drupal Code Generator, backing bundle-class:generate), and ContentTypeDiscoveryService (backs content-type:list). src/ConsoleCommandTrait.php and src/Trait/EnumDiscoveryTrait.php hold small helpers shared across commands.
  • If you enable/disable the module or add a new command class, run drush cache:rebuild (or clear caches however your site normally does) so the container is rebuilt before testing with dr.

About

Symfony Commands

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages