关于 drupal-contrib-mgmt
面向使用 Composer 管理 Drupal contrib 模块的开发者,提供模块更新、兼容性检查和补丁处理流程。内容重点包括 composer-patches 2.x 的锁文件同步、补丁验证及行为测试。
主要功能
- 01更新单个或多个模块
- 02检查 Drupal 11 兼容性
- 03管理 Drupal Lenient 配置
- 04应用并验证 Composer 补丁
- 05为补丁编写行为测试
- 06排查补丁应用问题
适用场景
- 01升级 Drupal contrib 模块版本
- 02为模块添加或维护补丁
- 03检查补丁锁文件与代码是否同步
- 04准备 Drupal 11 升级
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ grasmash/ drupal-claude-skills --skill drupal-contrib-mgmt安装后在 Claude Code 中输入/drupal-contrib-mgmt确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
examples/create-custom-patch.shfind-and-apply-patch.shmajor-version-upgrade.shupdate-with-patch.shreferences/contributing-upstream.mdd11-common-deprecations.mdd11-upgrade-workflow.mddrupal-lenient.mddrupal-patches-workflow.mdfinding-and-creating-patches.mdissue-queue-rss-feeds.mdupdate-patterns.md详细介绍
Drupal 贡献模块管理
在进行任何由 Composer 管理的贡献模块工作时,都使用此技能:更新模块、添加或调试补丁、解除版本约束限制、为 Drupal 11 做准备,或向上游提交修复。每次使用时都适用核心更新工作流和以下补丁规则;具体任务的详细信息位于 references/ 中(参见末尾的表格)。
核心更新工作流
标准模块更新
# Update a single module
composer require drupal/module_name --with-all-dependencies
# Update to specific version
composer require drupal/module_name:^3.0 --with-all-dependencies
# Update multiple modules
composer require drupal/module_a drupal/module_b --with-all-dependencies
# After any update, ALWAYS run database updates (updatedb rebuilds caches when it finishes)
drush updb -y
# On a deployed environment, use the full deploy tail instead; see the drupal-deploy-safety skill
# CRITICAL: Test by visiting pages to check for fatal errors
# Visit at least one page that uses the updated module
主版本升级
升级到新的主版本时(例如,2.x → 3.x):
- 检查兼容性:确保模块支持你的 Drupal 核心版本
- 搜索问题队列中的补丁:
https://www.drupal.org/project/issues/MODULE_NAME?categories=All - 使用 Drupal Lenient 处理版本要求问题(见下文)
- 通过 composer.json 应用补丁(参见补丁管理部分)
- 运行
upgrade_status检查弃用项
检查 Drupal 11 兼容性
三种方法(.info.yml、Composer、drupal.org)以及完整的 upgrade_status 工作流:参见 references/d11-upgrade-workflow.md。
Drupal Lenient 插件
mglaman/composer-drupal-lenient 插件允许安装尚未更新版本要求的模块。
Lenient/core-compat 覆盖:参见 references/drupal-lenient.md — 该参考文档介绍如何根据上游 composer.json/.info.yml 要求验证候选模块(而非本地打过补丁的副本)、运行 composer prohibits 检查,以及何时将模块添加到允许列表或从中移除。
设置
{
"require": {
"mglaman/composer-drupal-lenient": "^1.0"
},
"config": {
"allow-plugins": {
"mglaman/composer-drupal-lenient": true
}
},
"extra": {
"drupal-lenient": {
"allowed-list": [
"drupal/module_name",
"drupal/another_module"
]
}
}
}
用法
# Add module to allowed-list, then install
composer require drupal/module_name --with-all-dependencies
补丁管理(cweagans/composer-patches)
重要:使用 2.x 版本以确保可靠地应用补丁。1.x 版本使用 patch 二进制程序,在某些系统上可能会出现问题。2.x 版本默认使用 git apply。
补丁配置
{
"require": {
"cweagans/composer-patches": "^2.0"
},
"config": {
"allow-plugins": {
"cweagans/composer-patches": true
}
},
"extra": {
"composer-exit-on-patch-failure": true,
"patches": {
"drupal/module_name": {
"Description of patch": "https://www.drupal.org/files/issues/2024-01-15/module-issue-1234567-8.patch",
"Local patch": "patches/custom-fix.patch"
}
},
"patchLevel": {
"drupal/core": "-p2"
}
}
}
从 1.x 升级到 2.x
如果你使用的是 1.x 版本,并且遇到补丁应用失败:
composer require cweagans/composer-patches:^2.0 --with-all-dependencies
2.x 的主要区别:
- 使用
git apply,而不是patch二进制程序(更可靠) - 已移除
enable-patching选项(补丁始终启用) - 错误消息和调试功能更完善
- 关键 —
patches.lock.json的应用来源:每当 Composer 安装或更新软件包时(composer install、update、reinstall、patches-repatch),v2 都会从patches.lock.json应用补丁。这些命令都不会重新读取composer.json中的extra.patches:只有composer patches-relock或缺少patches.lock.json时,才会重新生成锁文件(composer-patches 2.0.0,Patches::loadLockedPatches())。因此,在composer.json中添加补丁后运行composer install或composer update,在重新锁定之前,该补丁不会生效。这是补丁“不断回退”的首要原因:本地开发环境看起来已修复(你手动应用了补丁或运行了update),但下一次全新安装——CI、队友的环境或新的部署——读取的是过时的锁文件,于是丢弃该补丁。修改extra.patches后务必运行composer patches-relock,并提交patches.lock.json。
验证补丁是否已应用
三种不同的问题,一项检查即可发现:
-
锁文件同步过时(根本原因):补丁已在
composer.json的extra.patches中注册,但由于跳过了composer patches-relock,从未添加到patches.lock.json。v2 在composer install时从锁文件应用补丁,因此每次全新安装时,该补丁都会被静默跳过。解决方法是重新锁定;这项检查的作用是通过断言composer.json中的每个本地补丁都存在于patches.lock.json中,来发现被跳过的情况。 -
已提交文件与工作区不一致(提交贡献代码的项目):补丁已应用到工作区,但产生的贡献模块文件更改从未提交到 git。任何从已提交的 git 状态部署、且不运行
composer install的平台(例如 Pantheon)都看不到这些更改,因此生产环境会静默运行未打补丁的代码。本地开发环境看起来正常。 -
补丁哈希缓存过期:即使锁文件同步,意外重新安装或供应商更新也可能导致跳过补丁的重新应用。与(1)和(2)相比,这种情况很少见,但同样可以通过已物化文件检查捕获。
解决方案:项目应有一个补丁验证脚本(例如 scripts/verify-patches.sh;本技能不提供该脚本),并在每次推送前以及 CI 中运行。脚本应执行以下操作:
- 锁文件同步检查:
composer.json中extra.patches下的每个本地补丁也必须出现在patches.lock.json中,以捕获跳过patches-relock的情况。 - 已物化文件检查(适用于提交 contrib 代码的项目):补丁修改的行必须存在于已提交的 contrib 文件中,以捕获“已打补丁但未提交”的情况。
- 从
composer.json中的extra.patches派生验证列表,无需手动整理。添加补丁条目后,检查就应能自动将其纳入。 - 对每个本地补丁(值以
patches/开头),解析所有+++ b/<path>标头,提取几行有辨识度的新增内容(例如,最多 5 行,每行至少包含 8 个非空白字符,且不是同一补丁中任何-行的子串),然后在目标文件中搜索这些内容。注意drupal/core软件包的core/路径前缀:核心补丁以 Drupal 根目录为基准(b/core/lib/...,应用时使用-p2),因此应将路径映射到<docroot>/core/...,而不是在软件包安装路径前添加前缀。 - 跳过基于 URL 的补丁(
https://...),并显示提示;如果补丁很关键,请在patches/下添加本地镜像。 - 在 CI 中,于
composer install之前运行该脚本,以验证已提交的代码树,而非安装后的状态。关键就在于这个执行顺序。
添加新补丁(重新生成锁文件这一步最容易被忘记):
- 将
.patch文件放入patches/ - 在
composer.json的extra.patches下登记该补丁 - 运行
composer patches-relock,将补丁添加到patches.lock.json。如果不执行这一步,第 4 步将不会应用任何补丁(v2 读取的是锁文件,而不是composer.json)。 - 运行
composer reinstall drupal/module_name(或composer patches-repatch),将补丁应用到工作树;v2 仅在 Composer 安装或更新软件包时才会为其打补丁,因此,若模块已安装,单纯运行composer install不会重新为其打补丁。 git add并提交composer.json、patches.lock.json和新的.patch文件。如果项目会提交 contrib 代码,也要提交修改后的 contrib 文件——对于从 git 部署且不运行composer install的平台,无法自行应用补丁,因此已提交的 contrib 文件必须已经包含补丁。- 为补丁修改的功能编写行为测试(见下文)。
- 推送前,在本地运行项目的补丁验证检查。
- 要求 CI 在每次推送时重新运行同一验证。
每个补丁都必须附带行为测试。 补丁验证检查是结构性检查:它能证明已提交文件中存在补丁的代码行,但不能证明打过补丁的代码行为正确。补丁可能已应用,却仍未解决任何问题(代码块位置错误、上游重构移动了相关逻辑,或后续补丁撤销了它)。行为测试能让补丁在模块升级时仍可靠:
- 反向案例:测试补丁要修复的确切边界条件。对于新补丁,先针对未打补丁的模块编写此测试,并确认它因报告的问题而失败,否则就无法证明该测试确实覆盖了这个缺陷。
- 正向案例:确保正常路径仍然有效(没有回归)。
- 将测试放在使用该功能的自定义模块的
tests/目录中,并在测试的文档块中引用.patch文件,以便升级模块的人能够找到它。 - 如果某个打过补丁的模块没有行为测试,绝不要升级它。 先编写测试,再升级,然后确认测试仍然通过(或者补丁已进入上游,可以移除)。 验证报告提示缺少补丁时:
- 锁定文件同步失败 → 有人跳过了
composer patches-relock(步骤 3)。修复方法:运行该命令,提交patches.lock.json并推送。 - 补丁文件物化失败 → 有人忘记提交已打补丁的 contrib 文件(步骤 5)。修复方法:在本地运行
composer patches-relock && composer patches-repatch,使用git add添加已打补丁的 contrib/core 目录(例如web/modules/contrib web/core或docroot/modules/contrib docroot/core)以及patches.lock.json,然后提交并推送。
注意事项:
- “组合补丁”(一个
.patch文件包含多个针对同一文件的+++ b/<same_file>标头,通常是合并了存在冲突代码块的提交)可能会漏检——只要检查接受了任何一行有辨识度的新增代码,即使只部分匹配也会通过。如果发现patches/中的补丁包含多个修改同一文件的代码块,请将其重新生成为干净的单提交差异。 - PHPCS:提交已打补丁的 contrib 文件可能会触发提交前的
phpcs任务(例如 GrumPHP),导致上游代码中原有的 sniff 违规报错。请将该任务配置为忽略 contrib、core 和 libraries 目录,而不是“修复”上游代码。
查找补丁
Issue Queue 搜索:https://www.drupal.org/project/issues/MODULE_NAME?categories=All
补丁命名约定:
- 格式:
module-issue-NODEID-COMMENT.patch - 示例:
audiofield-d11-3432063-12.patch - Node ID 是 issue 编号(访问
drupal.org/node/NODEID)
更新后现有补丁失效时:
- 从补丁文件名中提取 node ID(例如上面的
3432063) - 访问
https://www.drupal.org/node/3432063 - 在最新评论中查找更新后的补丁
- 使用新的补丁 URL 更新 composer.json
调试错误和创建本地补丁
编写补丁之前,先在 issue queue 中搜索现有补丁;分步搜索方法和独立克隆仓库的补丁工作流见 references/finding-and-creating-patches.md。
应用补丁
# Fresh checkout: packages are installed and patched from patches.lock.json
composer install
# After ANY change to extra.patches (add, edit, remove), relock first
composer patches-relock
# Re-patch a single module (most common). A plain `composer install` or
# `composer update` does nothing for a package that is already installed at
# the locked version, so its patches are not re-applied.
composer reinstall drupal/module_name
# Re-patch ALL patched dependencies (deletes and reinstalls them)
composer patches-repatch
详细的补丁工作流请参阅: references/drupal-patches-workflow.md
Drupal 11 兼容性工作流
六步 upgrade_status 工作流(分析、识别、修复自定义代码、.info.yml 补丁、宽松列表、验证)见 references/d11-upgrade-workflow.md。
完整更新检查清单
- 检查当前模块版本:
composer show drupal/module_name - 在 issue queue 中搜索已知问题
- 检查模块是否兼容 D11
- 使用新版本更新 composer.json
- 如有需要,添加到 drupal-lenient
- 搜索并应用所需补丁
- 确认模块上的每个现有补丁都有行为测试,并在升级后运行测试
- 运行
composer require drupal/module_name:^X.0 --with-all-dependencies - 运行
drush updb -y(它会在最后重建缓存;在已部署的环境中运行时,请执行drupal-deploy-safety技能中的完整收尾步骤) - 运行
drush upgrade_status:analyze module_name - 访问相关页面,测试模块功能
- 检查日志中的 PHP 错误/警告
- 使用描述性消息提交更改
故障排除
补丁无法应用
# Error: "Cannot apply patch..."
# 1. Check if module version changed
composer show drupal/module_name
# 2. Search issue queue for updated patch
# Visit drupal.org/node/NODEID (from patch filename)
# 3. Update composer.json with new patch URL
# 4. Or remove patch if merged upstream
# 5. Either way: composer patches-relock && composer reinstall drupal/module_name
版本冲突
# Error: "drupal/module_name requires drupal/core ^9"
# Add to drupal-lenient allowed-list
补丁已应用
# Error: "patch ... has already been applied"
# Module maintainer merged the patch - remove from composer.json, then
composer patches-relock
composer reinstall drupal/module_name
数据库更新失败
# Error during drush updb
# 1. Check error message carefully
# 2. Last resort only: pm:uninstall DELETES the module's config and stored data.
# Back up the database first, and never do this on a module holding data you need.
drush pm:uninstall module_name
composer require drupal/module_name --with-all-dependencies
drush pm:enable module_name
drush updb -y
最佳实践
- 模块更新时始终使用
--with-all-dependencies - composer 更新后始终运行
drush updb - 更新后立即测试(访问页面、检查日志)
- 将补丁整理在
patches/目录中 - 使用描述性名称和注释记录补丁
- 创建自定义补丁之前先检查 issue queue
- 使用 upgrade_status 验证 D11 兼容性
- 原子化提交:每次提交只更新一个模块
- 使用包含补丁引用的描述性提交消息
- 尽量精简 drupal-lenient 列表(仅在必要时添加)
生产部署
对于部署已提交 Git 状态的主机(服务器上不运行 composer install),请在不包含开发依赖包的情况下构建并提交 vendor:
composer update drupal/module_name --with-all-dependencies # the change itself
composer install --no-dev -o # then build the vendor you commit
git add composer.json composer.lock vendor/ # vendor/autoload.php + vendor/composer/ together
- 切勿提交之后运行普通
composer install留下的开发版自动加载器。 - 将
vendor/autoload.php、vendor/composer/和各个包目录一起暂存;部分暂存会导致全站范围的ComposerAutoloaderInit... not found致命错误。关于固定config.autoloader-suffix、三个后缀文件和提交前检查,请参阅drupal-deploy-safety技能(§8)。 - 推送只会更改代码。在目标环境中,运行部署收尾流程(循环执行
updatedb→cache:rebuild→config:import→cache:rebuild→deploy:hook,然后验证),不要只运行drush cr;请参阅drupal-deploy-safety技能(§2)。各主机的远程 Drush 命令形式请参阅drupal-config-mgmt技能。
开发和贡献 Contrib 模块
符号链接开发工作流以及 drupal.org 问题分支 / 合并请求工作流,详见 references/contributing-upstream.md。包含完整步骤的更新示例(已知补丁、D11 修复、破坏性大版本升级)详见 references/update-patterns.md。
参考资料
| 文件 | 适用场景 |
|---|---|
| references/d11-upgrade-workflow.md | 检查模块是否支持 Drupal 11,或运行 upgrade_status 扫描并修复扫描结果 |
| references/d11-common-deprecations.md | 替换特定的弃用常量、函数、类常量或 Twig 过滤器 |
| references/drupal-lenient.md | 判断模块是否应加入或可以从 drupal-lenient 允许列表中移除 |
| references/finding-and-creating-patches.md | 遇到 Drupal 错误并查找现有补丁,或从干净克隆中编写新的本地补丁 |
| references/drupal-patches-workflow.md | 任何更深入的 composer-patches 问题:插件命令、远程 / 本地 / MR 差异补丁、在已打补丁的模块上叠加补丁 |
| references/issue-queue-rss-feeds.md | 以编程方式查询 drupal.org 问题队列(RSS 筛选器、curl/WebFetch 解析) |
| references/update-patterns.md | 按照完整步骤处理已打补丁的更新、D11 contrib 修复,或可回滚的大版本升级 |
| references/contributing-upstream.md | 通过符号链接在本地开发 contrib 模块,或通过问题分支和合并请求贡献修复 |
| examples/ | 可运行的 shell 示例,演示上述相同场景 |
参考链接
- Composer Patches:https://github.com/cweagans/composer-patches
- Drupal Lenient:https://github.com/mglaman/composer-drupal-lenient
- Upgrade Status Module:https://www.drupal.org/project/upgrade_status
- Drupal 11 弃用项:https://www.drupal.org/about/core/policies/core-change-policies/drupal-deprecation-policy
- 补丁命名规范:https://www.drupal.org/node/1054616
- 创建问题分支:https://www.drupal.org/docs/develop/git/using-gitlab-to-contribute-to-drupal/creating-issue-forks
- 问题报告指南:https://www.drupal.org/community/contributor-guide/reference-information/quick-info/creating-or-updating-an-issue-report
- 文本格式提示:https://www.drupal.org/filter/tips
- Drupal 的 Git 工作流:https://www.drupal.org/docs/develop/git/using-git-to-contribute-to-drupal