Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

commands

A Drupal module providing standalone Drush 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).

Requirements

  • Drupal ^10 || ^11
  • drush/drush ~13 — commands are Symfony Console classes (#[AsCommand]), registered explicitly via drush.services.yml rather than relying on Drush's classic auto-discovery (see Development notes)
  • 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)

Installation

composer require previousnext/commands   # or however this repo is required
drush en commands

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 drush list --filter=content-type on your own site to confirm what's registered, and drush help <command> for full option details.

content-type:create

Creates a node type and saves it to active config.

drush content-type:create research "Research" --description="Research content"
drush 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.
drush content-type:field:add research introduction --reuse

# Create a new field.
drush content-type:field:add research pub_date --type=datetime --label="Publication date"
drush 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.

drush 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}
drush 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.

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

# With an enum case:
drush 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 drush php:eval one-liners for.

drush content-type:list field-storages
drush content-type:list node-types
drush content-type:list roles
drush content-type:list workflows
drush content-type:list modules
drush content-type:list enums
drush content-type:list entity-types
drush 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 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 drush 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, not a DrushCommands/Robo-style command — except nothing here currently uses that style. This matters because Drush's classic auto-discovery (CommandFileDiscovery) only picks up DrushCommands subclasses; Symfony Command classes must be explicitly registered as console.command-tagged services in drush.services.yml, or Drush never sees them at all.
  • drush.services.yml does not support _defaults: autowire. Drush's LegacyServiceInstantiator skips the entire file if it sees autowire: true anywhere in it — not just that one service, the whole file, silently (only an info-level log line explains why). Every argument in drush.services.yml must be listed explicitly.
  • All console.command-tagged services are eagerly instantiated at Drush boot, for every command, on every invocation — not lazily, and not just for the command being run. A broken constructor (wrong type-hint, missing service) in any one command's class takes down every content-type:* command until it's fixed. Run drush cache:rebuild after changing this module's services or commands, and test with drush list --filter=content-type before assuming a single command's fix is isolated.
  • 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.

About

Symfony Commands

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages