Правки у встановленому модулі
Модуль на сайті — це копія пакета. Оновлення замінює цю копію, тож будь-який
відредагований файл у app/Modules/<Name>/ тимчасовий за визначенням: наступний
реліз модуля — або нічний cms9:auto-update — принесе свої файли.
Мовчки ECMS9 цього не зробить. Кожен пакет везе .baseline.json (sha256 на
кожен файл, пише збирач пакетів), і інсталятор звіряє з ним дерево, перш ніж
щось чіпати:
- файли, які відрізняються або додані, зупиняють оновлення з
local_changesі перелічуються поіменно; - автооновлювач пропускає модуль і піднімає банер в адмінці замість перезапису;
- правки записуються патчем — див. нижче, — щоб їх можна було перенести в нову версію.
Тобто правка переживає оновлення, але не стає постійною. Підтримані способи змінити поведінку — нижче на цій сторінці; беріть їх для всього, що має жити довше за одне оновлення.
Подивитись, що змінив цей сайт: cms9:module-drift
php spark cms9:module-drift # усі модулі з baseline
php spark cms9:module-drift my_module # один модуль
php spark cms9:module-drift my_module --diff # ... і самі правки
php spark cms9:module-drift --all --json # машинний вивід
Команда лише читає: вона ніколи не пише в модуль, а без --diff не робить
жодного мережевого запиту — відповідь бере з .baseline.json на диску.
Коди виходу зроблені для CI і скриптів розкатки:
| код | що означає |
|---|---|
0 | нічого не відрізняється від пакетів |
1 | щонайменше один модуль правили на цьому сайті |
7 | помилка виклику (невідома назва модуля) |
Що --diff може показати, а що ні
--diff потрібні оригінальні байти саме тієї версії, що стоїть тут. Сайт їх
не зберігає (.baseline.json тримає хеші, не файли), тож вони тягнуться з
каталогу — а download каталогу віддає лише найновіший пакет модуля. Два
наслідки, і обидва проговорені у виводі, а не обійдені здогадкою:
- якщо на сайті версія старіша за каталог, оригіналу тієї ж версії в каталозі нема: команда каже це прямо й перелічує файли без diff;
downloadштампуєlast_seenу рядку ліцензії на сервері оновлень, тож--diff(і тільки він) чіпає ліцензію. Звичайні прогони — ні.
Завантажений пакет розпаковується в writable/store_drift_tmp/ і прибирається,
коли команда завершується.
Патч: writable/store_local_changes/
Обидва моменти, коли оновлення зустрічає руками правлений модуль, пишуть
writable/store_local_changes/<Studly>-<версія>-<дата>.patch:
- оновлення відмовлено (
local_changes) — з CLI, з вкладки «Магазин» чи з автоматичного прогонуcms9:auto-update; - оновлення свідомо перезаписало правки (
--overwrite-localабо «Оновити все одно» у вкладці «Магазин»).
Файл — unified diff зі шляхами a/ b/ відносно кореня модуля:
cd app4/app/Modules/MyModule
patch -p1 --dry-run < /шлях/до/writable/store_local_changes/MyModule-1.0.4-20260924-011500.patch
# конфліктів нема → запустіть ще раз без --dry-run
# конфлікти → файли *.rej тримають хунки, за якими треба рішення
Коментар-шапка в самому патчі повторює ці два рядки, називає модуль і версію та каже, чому патч написано.
Там, де оригінал не доводиться — каталог уже не має тієї версії або файл
бінарний, — нічого не вигадується: увесь змінений файл кладеться поруч із
патчем (<та сама назва>/files/… плюс manifest.json), а патч перелічує ці
файли. Патч, який вгадав оригінал, наклався б чисто й дав неправильний код — це
гірше, ніж відсутність патча.
Локально видалений файл патч називає, але сам не видаляє: оновлення його поверне, а прибрати вдруге — рішення людини.
Запис патча робиться за принципом «як вийде». Не вийшло — оновлення однаково їде (або однаково відмовлене), а в лог іде попередження: патч не має права бути причиною, через яку сайт не може оновитись.
--overwrite-local ще й переносить усе змінене дерево в
writable/store_backups/<Studly>-<дата>/, а cms9:module-rollback його
повертає. Патч — переносна форма того самого: бекап — це старий модуль, патч
— лише ваша частина в ньому.
Куди перенести правки, щоб вони жили
Патч, який накладають після кожного релізу, — рутина, а не рішення. Три підтримані місця для зміни:
1. Тема, коли зміна — це подача
Шаблони, розмітка, CSS і JS вітрини належать темі, а тема не входить у жоден
пакет — вона переживає будь-яке оновлення ядра й модулів. Віджет модуля, який
має виглядати інакше, перекривають у темі, а не правлять у app/Modules/.
2. Власний маленький модуль, що слухає події
Це загальна відповідь для поведінки. Власний модуль ніколи не конфліктує з
оновленням чужого, а auto-discovery CI4 підхоплює його Config/Events.php, щойно
простір імен відомий:
// app/Modules/MyShopTweaks/Config/Events.php
use CodeIgniter\Events\Events;
Events::on('order_placed', static function (array $payload): void {
// Слухач не має права зламати операцію, яку спостерігає.
if (! module_enabled('my_shop_tweaks')) {
return;
}
try {
service('myTweaks')->exportOrder((int) $payload['order_id']);
} catch (\Throwable $e) {
log_message('error', 'my_shop_tweaks: ' . $e->getMessage());
}
});
Розмітка вітрини додається так само — через
точки рендеру, слухачем
storefront_render_point:<name>, а не правкою в'юх.
3. Попросити подію в модулі
Якщо модуль, який треба змінити, не кидає події там, де вона потрібна, — правильна правка це подія в тому модулі, випущена разом із ним, а не локальна правка його коду. Це маленька зміна, яку видно на рев'ю і яку потім отримує кожен сайт; різниця між однорядковим патчем у модулі й патчем, який ви накладаєте вічно.
Чеклист
- Правили модуль, щоб щось запрацювало? Прогоніть
cms9:module-drift --diffі збережіть вивід — це і є специфікація того, що треба перенести. - Переносите в тему чи власний модуль? Робіть це до наступного релізу правленого модуля, а не після.
- Перенести не можна (події ще нема)? Заведіть задачу на подію й очікуйте, що
оновлення зупинятиметься на
local_changes, доки її нема, — з патчем, що чекає вwritable/store_local_changes/. - Наклали патч? Прогоніть
cms9:module-driftще раз: він має показати рівно ті файли, які ви свідомо лишили, і нічого більше.
Див. також: Структура модуля, Точки рендеру, Магазин і оновлення.