[{"data":1,"prerenderedAt":1594},["ShallowReactive",2],{"navigation_docs":3,"-modules-problems-build-and-install":238,"-modules-problems-build-and-install-surround":1591},[4,45,60],{"title":5,"description":6,"icon":7,"path":8,"stem":9,"children":10},"Методология",null,"ruler","\u002Fmethodology","1.methodology",[11,15,20,25,30,35,40],{"title":12,"path":8,"stem":13,"description":14},"bxshef: методология и проверка навыков","1.methodology\u002Findex","Как писать навыки ИИ-агентов для Битрикса и как проверять, что им можно верить",{"title":16,"path":17,"stem":18,"description":19},"Стандарт навыка","\u002Fmethodology\u002Fstandard","1.methodology\u002F1.standard","11 правил, каждое из провала на стенде",{"title":21,"path":22,"stem":23,"description":24},"Методология проверки","\u002Fmethodology\u002Fmethod","1.methodology\u002F2.method","lint → eval → стенд; что измерено",{"title":26,"path":27,"stem":28,"description":29},"bxshef — CLI","\u002Fmethodology\u002Fbxshef","1.methodology\u002F3.bxshef","lint, eval, feedback",{"title":31,"path":32,"stem":33,"description":34},"GitHub Action","\u002Fmethodology\u002Faction","1.methodology\u002F4.action","тот же lint + eval в любом репозитории навыков",{"title":36,"path":37,"stem":38,"description":39},"Заготовка репозитория","\u002Fmethodology\u002Ftemplate","1.methodology\u002F5.template","с чего начать автору модуля",{"title":41,"path":42,"stem":43,"description":44},"Приёмник отзывов","\u002Fmethodology\u002Ffeedback","1.methodology\u002F6.feedback","куда уходят отзывы ИИ-агентов",{"title":46,"description":6,"icon":47,"path":48,"stem":49,"children":50},"Навыки","sparkles","\u002Fskills","2.skills",[51,55],{"title":52,"path":48,"stem":53,"description":54},"Навыки shef.*","2.skills\u002Findex","npx skills add bx-shef\u002Fskills",{"title":56,"path":57,"stem":58,"description":59},"shef-feedback","\u002Fskills\u002Fshef-feedback","2.skills\u002F1.shef-feedback","Отправить отзыв о навыке shef-* или навыке команды. Брать ВСЕГДА, когда просят оставить или отправить отзыв о навыке — даже если сам навык в диалоге не виден, —",{"title":61,"description":6,"icon":62,"path":63,"stem":64,"children":65},"Модули shef.*","package","\u002Fmodules","3.modules",[66,69,131,179],{"title":61,"path":63,"stem":67,"description":68},"3.modules\u002Findex","Что за чем ставить",{"title":70,"description":6,"path":71,"stem":72,"children":73},"shef.options","\u002Fmodules\u002Foptions","3.modules\u002F1.options\u002Findex",[74,76,81,86,91,96,101,106,110,114,119,123,127],{"title":70,"path":71,"stem":72,"description":75},"фундамент: настройки, трейты, компоненты",{"title":77,"path":78,"stem":79,"description":80},"[`\\Shef\\Options\\Installator`] Installer","\u002Fmodules\u002Foptions\u002Finstaller","3.modules\u002F1.options\u002F1.installer","Для облегчения установок.",{"title":82,"path":83,"stem":84,"description":85},"Сборка, CI и релиз","\u002Fmodules\u002Foptions\u002Fbuild-and-install","3.modules\u002F1.options\u002F10.build-and-install","Раскладка репозитория — в module-structure.md, процесс —\nв CONTRIBUTING.md, установка глазами пользователя —\nв README.md.",{"title":87,"path":88,"stem":89,"description":90},"Раскладка репозитория","\u002Fmodules\u002Foptions\u002Fmodule-structure","3.modules\u002F1.options\u002F11.module-structure","Файл про устройство репозитория. Опорные точки модуля — в CLAUDE.md,\nпроцесс — в CONTRIBUTING.md, сборка — в\nbuild-and-install.md.",{"title":92,"path":93,"stem":94,"description":95},"Проверка на портале","\u002Fmodules\u002Foptions\u002Fportal-check","3.modules\u002F1.options\u002F12.portal-check","Всё, что ниже рантайма Битрикса, тестами не закрыть: установка, права, кеш,\nраскладка файлов, поведение при обновлении. Проверять это приходится руками —\nи лучше по списку, потому что забытый шаг находит не разработчик, а клиент.",{"title":97,"path":98,"stem":99,"description":100},"[`\\Shef\\Options\\Main\\Options`] Опции настроек модуля","\u002Fmodules\u002Foptions\u002Foptions","3.modules\u002F1.options\u002F2.options","Поддерживает следующие типы:",{"title":102,"path":103,"stem":104,"description":105},"Работа с пользователями","\u002Fmodules\u002Foptions\u002Fsecurity","3.modules\u002F1.options\u002F3.security","",{"title":107,"path":108,"stem":109,"description":105},"[`\\Shef\\Options\\Main\\Utils`] Утилиты","\u002Fmodules\u002Foptions\u002Futils","3.modules\u002F1.options\u002F4.utils",{"title":111,"path":112,"stem":113,"description":105},"[`\\Shef\\Options\\Components`] Работа с компонентами","\u002Fmodules\u002Foptions\u002Fcomponents","3.modules\u002F1.options\u002F5.components",{"title":115,"path":116,"stem":117,"description":118},"[`\\Shef\\Options\\Options`] Паттерны","\u002Fmodules\u002Foptions\u002Fpattern","3.modules\u002F1.options\u002F6.pattern","Запускаемые примеры: singleton.php,\nconfig.php, smartstd.php —\nкак их гонять, написано в examples\u002FREADME.md.",{"title":120,"path":121,"stem":122,"description":105},"[`\\Shef\\Options\\Tests`] Тестирование","\u002Fmodules\u002Foptions\u002Ftests","3.modules\u002F1.options\u002F7.tests",{"title":124,"path":125,"stem":126,"description":105},"[`\\Shef\\Options\\TraitList`] Набор трейтов","\u002Fmodules\u002Foptions\u002Ftraitlist","3.modules\u002F1.options\u002F8.traitlist",{"title":128,"path":129,"stem":130,"description":105},"Правила для ИИ-агентов в этом репозитории","\u002Fmodules\u002Foptions\u002Fagent-rules","3.modules\u002F1.options\u002F9.agent-rules",{"title":132,"description":6,"path":133,"stem":134,"children":135},"shef.problems","\u002Fmodules\u002Fproblems","3.modules\u002F2.problems\u002Findex",[136,138,143,147,152,157,161,166,169,172,175],{"title":132,"path":133,"stem":134,"description":137},"логи, журнал событий, учёт проблем",{"title":139,"path":140,"stem":141,"description":142},"[`\\Shef\\Problems\\Integration`] События и меню","\u002Fmodules\u002Fproblems\u002Fevents","3.modules\u002F2.problems\u002F1.events","Что модуль регистрирует в ядре при установке. Список — в .settings.php, ключ\ninstallEvents; что каждый обработчик существует, сверяет\ntests\u002Fsettings_test.php.",{"title":144,"path":145,"stem":146,"description":105},"Безопасность логов","\u002Fmodules\u002Fproblems\u002Fsecurity","3.modules\u002F2.problems\u002F10.security",{"title":148,"path":149,"stem":150,"description":151},"Быстрая отладка: `_pr`, `_log`, `_log1`","\u002Fmodules\u002Fproblems\u002Fdeffunctions","3.modules\u002F2.problems\u002F2.deffunctions","Функции объявляет def-functions.php, подключается он из include.php.",{"title":153,"path":154,"stem":155,"description":156},"Уровни логирования","\u002Fmodules\u002Fproblems\u002Floglevel","3.modules\u002F2.problems\u002F3.loglevel","Уровни — стандарт PSR-3, в Monolog это \\Monolog\\Level. Ниже — когда какой брать.",{"title":158,"path":159,"stem":160,"description":105},"[`\\Shef\\Problems\\Integration\\Monolog`] Monolog","\u002Fmodules\u002Fproblems\u002Fmonolog","3.modules\u002F2.problems\u002F4.monolog",{"title":162,"path":163,"stem":164,"description":165},"Ротация логов","\u002Fmodules\u002Fproblems\u002Flogrotate","3.modules\u002F2.problems\u002F5.logrotate","Логи модуля лежат в каталоге логов — по умолчанию на уровень выше корня сайта,\nдля BitrixVM \u002Fhome\u002Fbitrix\u002Fsh_log\u002F*.log, — и сами не чистятся. Ротацию делает\nlogrotate — системная утилита, а не Monolog: Monolog тоже умеет\n(RotatingFileHandler), но тогда за файлами следит каждый PHP-процесс, а не\nодна служба.",{"title":128,"path":167,"stem":168,"description":105},"\u002Fmodules\u002Fproblems\u002Fagent-rules","3.modules\u002F2.problems\u002F6.agent-rules",{"title":82,"path":170,"stem":171,"description":85},"\u002Fmodules\u002Fproblems\u002Fbuild-and-install","3.modules\u002F2.problems\u002F7.build-and-install",{"title":87,"path":173,"stem":174,"description":90},"\u002Fmodules\u002Fproblems\u002Fmodule-structure","3.modules\u002F2.problems\u002F8.module-structure",{"title":92,"path":176,"stem":177,"description":178},"\u002Fmodules\u002Fproblems\u002Fportal-check","3.modules\u002F2.problems\u002F9.portal-check","Всё, что ниже рантайма Битрикса, тестами не закрыть: установка, права, меню,\nраскладка файлов, журнал событий, поведение при обновлении. Проверять это\nприходится руками — и лучше по списку, потому что забытый шаг находит не\nразработчик, а клиент.",{"title":180,"description":6,"path":181,"stem":182,"children":183},"shef.insync","\u002Fmodules\u002Finsync","3.modules\u002F3.insync\u002Findex",[184,186,191,194,198,203,208,213,218,222,227,232,235],{"title":180,"path":181,"stem":182,"description":185},"агенты, импорт, API-клиенты, модели",{"title":187,"path":188,"stem":189,"description":190},"[`\\Shef\\InSync\\Agents`] Агенты","\u002Fmodules\u002Finsync\u002Fagents","3.modules\u002F3.insync\u002F1.agents","Агент импорта — наследник \\Shef\\InSync\\Agents\\AAgent. Описание агента для\nb_agent — \\Shef\\InSync\\Agents\\Entity, установку и управление берёт на себя\n\\Shef\\InSync\\Agents\\Manager.",{"title":87,"path":192,"stem":193,"description":90},"\u002Fmodules\u002Finsync\u002Fmodule-structure","3.modules\u002F3.insync\u002F10.module-structure",{"title":92,"path":195,"stem":196,"description":197},"\u002Fmodules\u002Finsync\u002Fportal-check","3.modules\u002F3.insync\u002F11.portal-check","Всё, что ниже рантайма Битрикса, тестами не закрыть: установка, права, левое\nменю, раскладка компонентов, агенты, таблица импорта, поведение при\nобновлении. Проверять это приходится руками — и лучше по списку, потому что\nзабытый шаг находит не разработчик, а клиент.",{"title":199,"path":200,"stem":201,"description":202},"Безопасность","\u002Fmodules\u002Finsync\u002Fsecurity","3.modules\u002F3.insync\u002F12.security","Модуль даёт другим модулям заготовки для синхронизаций, и самые опасные места\nу него общие с ними: кто может запускать агенты и импорт, что ложится в каталоги\nимпорта, что уходит в SQL и в лог. Ниже — что держит модуль и что остаётся\nпроекту.",{"title":204,"path":205,"stem":206,"description":207},"[`\\Shef\\InSync\\Sync`] Импорт","\u002Fmodules\u002Finsync\u002Fimport","3.modules\u002F3.insync\u002F2.import","Импорт идёт в два шага через таблицу импорта \\Shef\\InSync\\Sync\\Model\\SyncTable\n(shef_insync_model):",{"title":209,"path":210,"stem":211,"description":212},"[`\\Shef\\InSync\\Api`] API","\u002Fmodules\u002Finsync\u002Fapi","3.modules\u002F3.insync\u002F3.api","Обращение к внешнему API по HTTP — наследник \\Shef\\InSync\\Api\\AConnector.",{"title":214,"path":215,"stem":216,"description":217},"[`\\Shef\\InSync\\TraitList\\Xml`] Парсинг XML","\u002Fmodules\u002Finsync\u002Fxml","3.modules\u002F3.insync\u002F4.xml","Разбор XML сделан через SbWereWolf\u002Fxml-navigator\n(статья на Хабре): XMLReader идёт по\nдокументу потоком, а каждый элемент с нужным тегом HierarchyComposer\nпревращает в массив — n имя, v значение, a атрибуты, s вложенные\nэлементы.",{"title":219,"path":220,"stem":221,"description":105},"Компоненты","\u002Fmodules\u002Finsync\u002Fcomponents","3.modules\u002F3.insync\u002F5.components",{"title":223,"path":224,"stem":225,"description":226},"Страницы","\u002Fmodules\u002Finsync\u002Fpage","3.modules\u002F3.insync\u002F6.page","Страницы модуля — в левом меню, штатным разделом intranet\n(Bitrix\\Intranet\\CustomSection): раздел «SH Импорт», код shinsync.\nВерхней панели и shef.uiclear больше нет.",{"title":228,"path":229,"stem":230,"description":231},"[`\\Shef\\InSync\\Main\\Options`] Опции настроек модуля","\u002Fmodules\u002Finsync\u002Foptions","3.modules\u002F3.insync\u002F7.options","Опции для страницы настроек модуля импорта (на ShOptionsConfig из\nshef.options): добавьте их во вкладку своего options_conf.php.",{"title":128,"path":233,"stem":234,"description":105},"\u002Fmodules\u002Finsync\u002Fagent-rules","3.modules\u002F3.insync\u002F8.agent-rules",{"title":82,"path":236,"stem":237,"description":85},"\u002Fmodules\u002Finsync\u002Fbuild-and-install","3.modules\u002F3.insync\u002F9.build-and-install",{"id":239,"title":82,"body":240,"description":85,"extension":1585,"meta":1586,"navigation":1587,"path":170,"rawbody":1588,"seo":1589,"stem":171,"__hash__":1590},"docs\u002F3.modules\u002F2.problems\u002F7.build-and-install.md",{"type":241,"value":242,"toc":1565},"minimark",[243,263,272,275,319,327,331,373,407,417,460,471,475,639,645,650,656,696,711,715,731,751,755,768,771,904,908,914,1022,1034,1050,1056,1064,1087,1094,1104,1108,1114,1134,1150,1153,1159,1168,1172,1190,1200,1204,1224,1227,1311,1323,1327,1359,1371,1388,1406,1412,1488,1508,1511,1525,1528,1536,1542,1549,1561],[244,245,246,247,251,252,258,259,262],"p",{},"Раскладка репозитория — в ",[248,249,250],"a",{"href":173},"module-structure.md",", процесс —\nв ",[248,253,257],{"href":254,"rel":255},"https:\u002F\u002Fgithub.com\u002Fbx-shef\u002Fproblems\u002Fblob\u002Fmain\u002FCONTRIBUTING.md",[256],"nofollow","CONTRIBUTING.md",", установка глазами пользователя —\nв ",[248,260,261],{"href":133},"README.md",".",[264,265,267,271],"h2",{"id":266},"buildsh-точка-входа-сборки-и-проверок",[268,269,270],"code",{},"build.sh"," — точка входа сборки и проверок",[244,273,274],{},"Вторая проверка — линтер, отдельной целью Composer: см. раздел ниже.",[276,277,281],"pre",{"className":278,"code":279,"language":280,"meta":105,"style":105},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark",".\u002Fbuild.sh            # проверки + архив shef.problems.zip\n.\u002Fbuild.sh --check    # только проверки\n.\u002Fbuild.sh --version  # напечатать версию модуля\n","bash",[268,282,283,296,308],{"__ignoreMap":105},[284,285,288,292],"span",{"class":286,"line":287},"line",1,[284,289,291],{"class":290},"sbgvK",".\u002Fbuild.sh",[284,293,295],{"class":294},"sutJx","            # проверки + архив shef.problems.zip\n",[284,297,299,301,305],{"class":286,"line":298},2,[284,300,291],{"class":290},[284,302,304],{"class":303},"stzsN"," --check",[284,306,307],{"class":294},"    # только проверки\n",[284,309,311,313,316],{"class":286,"line":310},3,[284,312,291],{"class":290},[284,314,315],{"class":303}," --version",[284,317,318],{"class":294},"  # напечатать версию модуля\n",[244,320,321,322,326],{},"CI зовёт ",[323,324,325],"strong",{},"её же",". Это не украшение: если бы сервер гонял свой набор команд,\nлокальный зелёный прогон и серверный красный означали бы разные вещи, и\nразбираться пришлось бы в двух местах сразу.",[264,328,330],{"id":329},"линтер-рядом-со-сборкой-а-не-внутри","Линтер — рядом со сборкой, а не внутри",[276,332,334],{"className":278,"code":333,"language":280,"meta":105,"style":105},"composer install         # один раз: инструменты разработчика в vendor-dev\u002F\ncomposer run lint        # сухой прогон: покажет диф и упадёт\ncomposer run lint:fix    # привести файлы\n",[268,335,336,348,361],{"__ignoreMap":105},[284,337,338,341,345],{"class":286,"line":287},[284,339,340],{"class":290},"composer",[284,342,344],{"class":343},"s_sjI"," install",[284,346,347],{"class":294},"         # один раз: инструменты разработчика в vendor-dev\u002F\n",[284,349,350,352,355,358],{"class":286,"line":298},[284,351,340],{"class":290},[284,353,354],{"class":343}," run",[284,356,357],{"class":343}," lint",[284,359,360],{"class":294},"        # сухой прогон: покажет диф и упадёт\n",[284,362,363,365,367,370],{"class":286,"line":310},[284,364,340],{"class":290},[284,366,354],{"class":343},[284,368,369],{"class":343}," lint:fix",[284,371,372],{"class":294},"    # привести файлы\n",[244,374,375,376,379,380,383,384,387,388,391,392,394,395,398,399,402,403,406],{},"php-cs-fixer, набор ",[268,377,378],{},"@PSR12"," целиком, правила — ",[268,381,382],{},".php-cs-fixer.dist.php",".\nВерсия инструмента пришпилена точно (",[268,385,386],{},"3.95.27",", без ",[268,389,390],{},"^","): набор ",[268,393,378],{},"\nпополняется в минорных выпусках, и с ",[268,396,397],{},"^3.0"," CI однажды покраснел бы на\nкоммите, который ничего не менял. Его зависимости держит ",[268,400,401],{},"composer.lock"," —\nон под git, вопреки обычаю для библиотек; ",[268,404,405],{},"config.platform.php"," = 8.2.0,\nиначе lock разрешился бы под PHP того, кто его собирал, и на 8.2 не встал бы.",[244,408,409,410,412,413,416],{},"Из ",[268,411,270],{}," линтер не зовётся: сборке хватает php, git и zip, и она обязана\nотрабатывать в свежем клоне. Позови она линтер — ",[268,414,415],{},".\u002Fbuild.sh --check"," зависел\nбы от сети.",[244,418,419,428,429,431,432,435,436,439,440,443,444,447,448,451,452,455,456,459],{},[323,420,421,424,425,262],{},[268,422,423],{},"vendor-dev\u002F",", а не ",[268,426,427],{},"vendor\u002F"," ",[268,430,427],{}," здесь — своя копия Monolog под\ngit, она едет в поставку. Поставь Composer инструменты туда, ",[268,433,434],{},"composer install","\nпереписал бы копию той версией Monolog, что разрешилась в lock. Плагин\n",[268,437,438],{},"composer\u002Finstallers"," в корневом ",[268,441,442],{},"composer.json"," выключен (",[268,445,446],{},"false","): иначе он\nразложил бы ",[268,449,450],{},"bxshef\u002Foptions"," в ",[268,453,454],{},"bitrix\u002Fmodules\u002F"," посреди репозитория.\nПотребителей пакета это не касается — ",[268,457,458],{},"config"," Composer читает только у\nкорневого проекта.",[244,461,462,463,466,467,470],{},"Линтер правит PHP-токены. Инлайновый HTML между ",[268,464,465],{},"?>"," и ",[268,468,469],{},"\u003C?php"," он не трогает —\nего раскладка доводится руками; содержимое строковых литералов не трогает ни\nон, ни рука: это данные программы.",[264,472,474],{"id":473},"что-проверяется","Что проверяется",[476,477,478,491],"table",{},[479,480,481],"thead",{},[482,483,484,488],"tr",{},[485,486,487],"th",{},"проверка",[485,489,490],{},"что ловит",[492,493,494,505,515,529,539,552,565,578,591,608,625],"tbody",{},[482,495,496,502],{},[497,498,499],"td",{},[268,500,501],{},"check_filenames",[497,503,504],{},"имя, с которым не справится скрипт; символическую ссылку под контролем git",[482,506,507,512],{},[497,508,509],{},[268,510,511],{},"check_lists",[497,513,514],{},"файл, не попавший ни в SHIP, ни в KEEP",[482,516,517,522],{},[497,518,519],{},[268,520,521],{},"check_gitattributes",[497,523,524,525,528],{},"расхождение KEEP и ",[268,526,527],{},"export-ignore"," — в обе стороны",[482,530,531,536],{},[497,532,533],{},[268,534,535],{},"check_encoding",[497,537,538],{},"файл не в UTF-8, BOM в начале файла",[482,540,541,546],{},[497,542,543],{},[268,544,545],{},"check_php",[497,547,548,551],{},[268,549,550],{},"php -l"," по всем PHP",[482,553,554,559],{},[497,555,556],{},[268,557,558],{},"check_short_tags",[497,560,561,562],{},"короткий тег ",[268,563,564],{},"\u003C?",[482,566,567,572],{},[497,568,569],{},[268,570,571],{},"check_js",[497,573,574,577],{},[268,575,576],{},"node --check"," по всем JS",[482,579,580,585],{},[497,581,582],{},[268,583,584],{},"check_lowercase",[497,586,587,588],{},"заглавные буквы в путях ",[268,589,590],{},"lib\u002F",[482,592,593,598],{},[497,594,595],{},[268,596,597],{},"check_version",[497,599,600,601,604,605],{},"пустой или кривой ",[268,602,603],{},"VERSION",", пустой ",[268,606,607],{},"VERSION_DATE",[482,609,610,615],{},[497,611,612],{},[268,613,614],{},"run_tests",[497,616,617,620,621,624],{},[268,618,619],{},"tests\u002F*_test.php"," (php) и ",[268,622,623],{},"tests\u002F*_test.mjs"," (node)",[482,626,627,632],{},[497,628,629],{},[268,630,631],{},"check_composer_package",[497,633,634,635,638],{},"состав ",[268,636,637],{},"git archive"," разошёлся со списком SHIP",[244,640,641,642,262],{},"При сборке дополнительно: состав zip сверяется со списком SHIP, а первый\nуровень внутри архива — с ",[268,643,644],{},"shef.problems\u002F",[646,647,649],"h3",{"id":648},"что-здесь-сделано-строже-чем-хотелось","Что здесь сделано «строже, чем хотелось»",[244,651,652,655],{},[323,653,654],{},"Пропущенная проверка выглядит как пройденная."," Поэтому:",[657,658,659,671,676],"ul",{},[660,661,662,663,666,667,670],"li",{},"нет ",[268,664,665],{},"node",", а JS или ",[268,668,669],{},"*_test.mjs"," в репозитории есть — отказ, а не примечание;",[660,672,673,675],{},[268,674,614],{}," сначала считает, сколько тестов ЕСТЬ, и сверяет с числом\nпрогнанных: переименованный файл или тест в подкаталоге иначе выпал бы из\nпрогона молча;",[660,677,678,680,681,684,685,688,689,692,693,695],{},[268,679,631],{}," сверяет ",[323,682,683],{},"индекс"," (",[268,686,687],{},"git write-tree","), а не ",[268,690,691],{},"HEAD",", и\nпотому работает и на грязном дереве. Раньше он на ней пропускался — а локально\nдерево грязное почти всегда, так что единственная проверка, ловящая\nрасхождения самого ",[268,694,637],{},", срабатывала только в CI.",[244,697,698,428,701,704,705,707,708,710],{},[323,699,700],{},"Символическая ссылка разводит каналы поставки молча.",[268,702,703],{},"cp"," в архив\nразыменовывает её и кладёт содержимое цели, ",[268,706,637],{}," кладёт саму ссылку, а\nобе сверки состава при этом зелёные — они сверяют имена. Поэтому ссылок под\nконтролем git просто не бывает: ",[268,709,501],{}," роняет сборку.",[646,712,714],{"id":713},"про-короткие-теги-отдельно","Про короткие теги отдельно",[244,716,717,719,720,723,724,727,728,730],{},[268,718,550],{}," их ",[323,721,722],{},"не ловит",". При ",[268,725,726],{},"short_open_tag=Off"," — а это значение по\nумолчанию — ",[268,729,564],{}," открывающим тегом не считается, и файл целиком становится\nинлайновым HTML. Синтаксически он остаётся правильным, в нём просто нет PHP.\nА на портале классы из такого файла не определяются, зато исходник уезжает в\nбраузер.",[244,732,733,734,736,737,740,741,743,744,747,748,262],{},"Поэтому ",[268,735,558],{}," спрашивает сам PHP через ",[268,738,739],{},"token_get_all()",", а не\ngrep: ",[268,742,564],{}," внутри строки или комментария лежит в своём токене, опасный же\nостаётся куском ",[268,745,746],{},"T_INLINE_HTML",". Наивный grep краснел бы на каждом регулярном\nвыражении вида ",[268,749,750],{},"\u002F\u003C?\u002F",[264,752,754],{"id":753},"архив","Архив",[244,756,757,758,761,762,764,765,767],{},"Архив содержит ",[323,759,760],{},"каталог модуля целиком",": первым уровнем внутри zip лежит\n",[268,763,644],{},", иначе при распаковке файлы рассыплются прямо по\n",[268,766,454],{},". Проверяется в самом скрипте, а не глазами.",[244,769,770],{},"Сверить поставку двумя путями установки можно так:",[276,772,774],{"className":278,"code":773,"language":280,"meta":105,"style":105},"git archive --format=tar \"$(git write-tree)\" | tar -tf - | grep -v '\u002F$' | sort > \u002Ftmp\u002Fcomposer.txt\n.\u002Fbuild.sh && unzip -Z1 shef.problems.zip | grep -v '\u002F$' | sed 's#^shef.problems\u002F##' | sort > \u002Ftmp\u002Fzip.txt\ndiff \u002Ftmp\u002Fcomposer.txt \u002Ftmp\u002Fzip.txt    # должно быть пусто\n",[268,775,776,840,890],{"__ignoreMap":105},[284,777,778,781,784,787,791,793,796,799,803,806,809,812,814,817,820,823,826,829,831,834,837],{"class":286,"line":287},[284,779,780],{"class":290},"git",[284,782,783],{"class":343}," archive",[284,785,786],{"class":303}," --format=tar",[284,788,790],{"class":789},"sjJ54"," \"$(",[284,792,780],{"class":290},[284,794,795],{"class":343}," write-tree",[284,797,798],{"class":789},")\"",[284,800,802],{"class":801},"smGrS"," |",[284,804,805],{"class":290}," tar",[284,807,808],{"class":303}," -tf",[284,810,811],{"class":343}," -",[284,813,802],{"class":801},[284,815,816],{"class":290}," grep",[284,818,819],{"class":303}," -v",[284,821,822],{"class":789}," '",[284,824,825],{"class":343},"\u002F$",[284,827,828],{"class":789},"'",[284,830,802],{"class":801},[284,832,833],{"class":290}," sort",[284,835,836],{"class":801}," >",[284,838,839],{"class":343}," \u002Ftmp\u002Fcomposer.txt\n",[284,841,842,844,848,851,854,857,859,861,863,865,867,869,871,874,876,879,881,883,885,887],{"class":286,"line":298},[284,843,291],{"class":290},[284,845,847],{"class":846},"sP7_E"," &&",[284,849,850],{"class":290}," unzip",[284,852,853],{"class":303}," -Z1",[284,855,856],{"class":343}," shef.problems.zip",[284,858,802],{"class":801},[284,860,816],{"class":290},[284,862,819],{"class":303},[284,864,822],{"class":789},[284,866,825],{"class":343},[284,868,828],{"class":789},[284,870,802],{"class":801},[284,872,873],{"class":290}," sed",[284,875,822],{"class":789},[284,877,878],{"class":343},"s#^shef.problems\u002F##",[284,880,828],{"class":789},[284,882,802],{"class":801},[284,884,833],{"class":290},[284,886,836],{"class":801},[284,888,889],{"class":343}," \u002Ftmp\u002Fzip.txt\n",[284,891,892,895,898,901],{"class":286,"line":310},[284,893,894],{"class":290},"diff",[284,896,897],{"class":343}," \u002Ftmp\u002Fcomposer.txt",[284,899,900],{"class":343}," \u002Ftmp\u002Fzip.txt",[284,902,903],{"class":294},"    # должно быть пусто\n",[264,905,907],{"id":906},"ci","CI",[244,909,910,913],{},[268,911,912],{},".github\u002Fworkflows\u002Fci.yml",", шесть задач:",[476,915,916,926],{},[479,917,918],{},[482,919,920,923],{},[485,921,922],{},"задача",[485,924,925],{},"что делает",[492,927,928,947,962,983,995,1010],{},[482,929,930,939],{},[497,931,932,935,936],{},[268,933,934],{},"PHP 8.2"," … ",[268,937,938],{},"PHP 8.5",[497,940,941,943,944],{},[268,942,415],{},", ",[268,945,946],{},"fail-fast: false",[482,948,949,954],{},[497,950,951],{},[268,952,953],{},"Composer",[497,955,956,959,960],{},[268,957,958],{},"composer validate --strict",": пакет ставят через Composer, и сломанный манифест виден только тому, кто ставит; заодно свежесть ",[268,961,401],{},[482,963,964,969],{},[497,965,966],{},[268,967,968],{},"Skills",[497,970,971,974,975,978,979,982],{},[268,972,973],{},"sync.sh --check"," против ",[268,976,977],{},"MANIFEST"," источника в ",[268,980,981],{},"bx-shef\u002Foptions",": навыки здесь — копия, и копия не должна отставать",[482,984,985,990],{},[497,986,987],{},[268,988,989],{},"Build",[497,991,992,994],{},[268,993,291],{}," плюс архив артефактом прогона",[482,996,997,1002],{},[497,998,999],{},[268,1000,1001],{},"Lint",[497,1003,1004,466,1006,1009],{},[268,1005,434],{},[268,1007,1008],{},"composer run lint",", одна версия PHP — 8.2",[482,1011,1012,1016],{},[497,1013,1014],{},[268,1015,907],{},[497,1017,1018,1019],{},"ворота, ",[268,1020,1021],{},"needs: [checks, composer, skills, build, lint]",[244,1023,1024,1029,1030,1033],{},[323,1025,1026,1028],{},[268,1027,968],{}," краснеет, когда навыки поправили в shef.options."," Это не поломка\nэтого репозитория, а сигнал: разложите навыки заново\n(",[268,1031,1032],{},"..\u002Foptions\u002F.claude\u002Fskills\u002Fsync.sh --to .",") и закоммитьте. Копию на месте не\nправят — правка будет затёрта следующей раскладкой.",[244,1035,1036,428,1039,1042,1043,1046,1047,262],{},[323,1037,1038],{},"Версии PHP в матрице — не только про код модуля.",[268,1040,1041],{},"tests\u002Fvendor_test.php","\nразбирает все файлы своей копии Monolog с ",[268,1044,1045],{},"error_reporting=-1",": новая версия PHP\nс новыми deprecation покраснеет здесь, а не в логе портала. Так уже было:\nMonolog 3.3.1 на PHP 8.4 сыпал deprecation из ",[268,1048,1049],{},"Monolog\\Logger",[244,1051,1052,1053,1055],{},"В защите ветки требуется ровно одна проверка — ",[268,1054,907],{},". Остальные её зависимости,\nпоэтому новая задача не потребует правки ruleset.",[646,1057,1059,1060,1063],{"id":1058},"что-в-ciyml-выглядит-ошибкой-но-ею-не-является","Что в ",[268,1061,1062],{},"ci.yml"," выглядит ошибкой, но ею не является",[244,1065,1066,1074,1075,1079,1080,1083,1084,262],{},[323,1067,1068,1071,1072],{},[268,1069,1070],{},"if: always()"," у задачи ",[268,1073,907],{}," — обязателен вместе с явной сверкой результатов\nзависимостей. Без него задача была бы ",[1076,1077,1078],"em",{},"пропущена"," при падении зависимости, а\nпропущенную проверку защита ветки засчитывает как ",[1076,1081,1082],{},"пройденную",": красный CI уехал\nбы в ",[268,1085,1086],{},"main",[244,1088,1089,1090,1093],{},"Подмывает заменить на ",[268,1091,1092],{},"!cancelled()"," — не надо. Тогда отменённый прогон стал бы\nдавать пропущенную проверку, и, отменив прогон вручную, можно было бы смержить\nнепроверенное.",[244,1095,1096,1103],{},[323,1097,1098,1099,1102],{},"Вытесненный по ",[268,1100,1101],{},"concurrency"," прогон краснеет"," на устаревшем коммите. Это шум,\nа не поломка: защита смотрит на проверки головного коммита.",[264,1105,1107],{"id":1106},"релиз","Релиз",[244,1109,1110,1113],{},[268,1111,1112],{},".github\u002Fworkflows\u002Frelease.yml",", два входа.",[244,1115,1116,1122,1123,1126,1127,1129,1130,1133],{},[323,1117,1118,1119],{},"Пуш тега ",[268,1120,1121],{},"v*"," — тег ",[323,1124,1125],{},"сверяется"," с ",[268,1128,603],{}," из ",[268,1131,1132],{},"install\u002Fversion.php",".\nРасхождение роняет прогон: тегу не доверяем, иначе на портал уедет архив,\nверсия которого врёт.",[244,1135,1136,1122,1144,1129,1147,1149],{},[323,1137,1138,1141,1142],{},[268,1139,1140],{},"workflow_dispatch"," от ",[268,1143,1086],{},[323,1145,1146],{},"выводится",[268,1148,603],{}," и ставится\nсам. Запуск от другой ветки отклоняется, занятый тег ловится до сборки.",[244,1151,1152],{},"Второй вход обязателен: пуш тегов бывает недоступен — другие права, прокси\nсессии, — а релиз выпускать надо.",[244,1154,1155,1158],{},[323,1156,1157],{},"Тег ставится после успешной сборки."," Поставленный раньше, он пережил бы\nупавшую сборку, и следующая попытка упёрлась бы в занятый тег.",[244,1160,1161,1162,451,1165,262],{},"Примечания к релизу собираются из секции ",[268,1163,1164],{},"## \u003Cверсия>",[268,1166,1167],{},"CHANGELOG.md",[646,1169,1171],{"id":1170},"packagist","Packagist",[244,1173,1174,1175,1178,1179,466,1182,1185,1186,1189],{},"Последним шагом релиз дёргает ",[268,1176,1177],{},"update-package",". Без секретов\n",[268,1180,1181],{},"PACKAGIST_USERNAME",[268,1183,1184],{},"PACKAGIST_TOKEN"," шаг пропускается, и релиз при этом\n",[323,1187,1188],{},"не падает",": невыложенный релиз чинить нечем, а отставший Packagist\nдогоняется кнопкой Update за десять секунд.",[244,1191,1192,1193,1196,1197,262],{},"Эндпойнт умеет только ",[323,1194,1195],{},"обновлять уже зарегистрированный"," пакет. Первую\nрегистрацию делают один раз руками: packagist.org → Submit →\n",[268,1198,1199],{},"https:\u002F\u002Fgithub.com\u002Fbx-shef\u002Fproblems",[264,1201,1203],{"id":1202},"monolog-composer-и-своя-копия","Monolog: Composer и своя копия",[244,1205,1206,1208,1209,1212,1213,1216,1217,1220,1221,262],{},[268,1207,442],{}," требует ",[268,1210,1211],{},"monolog\u002Fmonolog"," — через Composer он ложится в vendor\nпроекта. Архив несёт свою копию в ",[268,1214,1215],{},"vendor\u002Fmonolog\u002Fmonolog"," (SHIP). Какую\nподключать, решает ",[268,1218,1219],{},".settings.php"," при каждой загрузке, см.\n",[248,1222,1223],{"href":159},"Monolog",[244,1225,1226],{},"Обновить свою копию:",[276,1228,1230],{"className":278,"code":1229,"language":280,"meta":105,"style":105},"git clone --depth 1 --branch \u003Cверсия> https:\u002F\u002Fgithub.com\u002FSeldaek\u002Fmonolog.git \u002Ftmp\u002Fmonolog\nrm -rf vendor\u002Fmonolog\u002Fmonolog && mkdir -p vendor\u002Fmonolog\u002Fmonolog\ncp -a \u002Ftmp\u002Fmonolog\u002F{src,LICENSE,README.md,CHANGELOG.md,composer.json} vendor\u002Fmonolog\u002Fmonolog\u002F\n.\u002Fbuild.sh --check\n",[268,1231,1232,1268,1290,1303],{"__ignoreMap":105},[284,1233,1234,1236,1239,1242,1246,1249,1252,1255,1259,1262,1265],{"class":286,"line":287},[284,1235,780],{"class":290},[284,1237,1238],{"class":343}," clone",[284,1240,1241],{"class":303}," --depth",[284,1243,1245],{"class":1244},"srdBf"," 1",[284,1247,1248],{"class":303}," --branch",[284,1250,1251],{"class":801}," \u003C",[284,1253,1254],{"class":343},"верси",[284,1256,1258],{"class":1257},"su5hD","я",[284,1260,1261],{"class":801},">",[284,1263,1264],{"class":343}," https:\u002F\u002Fgithub.com\u002FSeldaek\u002Fmonolog.git",[284,1266,1267],{"class":343}," \u002Ftmp\u002Fmonolog\n",[284,1269,1270,1273,1276,1279,1281,1284,1287],{"class":286,"line":298},[284,1271,1272],{"class":290},"rm",[284,1274,1275],{"class":303}," -rf",[284,1277,1278],{"class":343}," vendor\u002Fmonolog\u002Fmonolog",[284,1280,847],{"class":846},[284,1282,1283],{"class":290}," mkdir",[284,1285,1286],{"class":303}," -p",[284,1288,1289],{"class":343}," vendor\u002Fmonolog\u002Fmonolog\n",[284,1291,1292,1294,1297,1300],{"class":286,"line":310},[284,1293,703],{"class":290},[284,1295,1296],{"class":303}," -a",[284,1298,1299],{"class":343}," \u002Ftmp\u002Fmonolog\u002F{src,LICENSE,README.md,CHANGELOG.md,composer.json}",[284,1301,1302],{"class":343}," vendor\u002Fmonolog\u002Fmonolog\u002F\n",[284,1304,1306,1308],{"class":286,"line":1305},4,[284,1307,291],{"class":290},[284,1309,1310],{"class":303}," --check\n",[244,1312,1313,1314,1316,1317,1319,1320,1322],{},"Версия копии обязана подходить под ограничение в ",[268,1315,442],{}," — иначе\nпоставленный архивом и поставленный Composer модуль работали бы на разном\nMonolog. Сторожит ",[268,1318,1041],{},", версию он читает из первой записи\n",[268,1321,1167],{}," копии.",[264,1324,1326],{"id":1325},"куда-composer-кладёт-модуль","Куда Composer кладёт модуль",[244,1328,1329,1331,1332,1335,1336,1339,1340,1343,1344,1347,1348,1351,1352,1355,1356,1358],{},[268,1330,442],{},": ",[268,1333,1334],{},"type"," = ",[268,1337,1338],{},"bitrix-module"," плюс\n",[268,1341,1342],{},"extra.installer-name = shef.problems",". Тогда Composer разворачивает модуль в\n",[268,1345,1346],{},"bitrix\u002Fmodules\u002Fshef.problems\u002F"," без настройки на стороне потребителя:\n",[268,1349,1350],{},"installer-name"," читается из пакета, а ",[268,1353,1354],{},"{$bitrix_dir}"," — только из корневого\n",[268,1357,442],{},", повлиять на него пакет не может.",[244,1360,1361,1367,1368,1370],{},[323,1362,1363,1366],{},[268,1364,1365],{},"bitrix-d7-module"," развернул бы модуль не туда."," Шаблоны в\n",[268,1369,438],{},":",[657,1372,1373,1381],{},[660,1374,1375,1377,1378],{},[268,1376,1338],{}," → ",[268,1379,1380],{},"{$bitrix_dir}\u002Fmodules\u002F{$name}\u002F",[660,1382,1383,1377,1385],{},[268,1384,1365],{},[268,1386,1387],{},"{$bitrix_dir}\u002Fmodules\u002F{$vendor}.{$name}\u002F",[244,1389,1390,1391,1393,1394,1397,1398,1401,1402,1405],{},"а ",[268,1392,1350],{}," подменяет только ",[268,1395,1396],{},"{$name}",". Для пакета ",[268,1399,1400],{},"bxshef\u002Fproblems","\nвторой вариант дал бы ",[268,1403,1404],{},"bitrix\u002Fmodules\u002Fbxshef.shef.problems\u002F"," — каталог, которого\nБитрикс не знает.",[244,1407,1408,1409,1370],{},"На стороне проекта-потребителя Composer 2.2+ требует явного разрешения\nплагина, иначе в неинтерактивном режиме (CI) он не отработает и пакет ляжет в\n",[268,1410,1411],{},"vendor\u002Fbxshef\u002Fproblems",[276,1413,1417],{"className":1414,"code":1415,"language":1416,"meta":105,"style":105},"language-json shiki shiki-themes material-theme-lighter github-light github-dark","{\n    \"config\": {\n        \"allow-plugins\": {\n            \"composer\u002Finstallers\": true\n        }\n    }\n}\n","json",[268,1418,1419,1424,1441,1456,1470,1476,1482],{"__ignoreMap":105},[284,1420,1421],{"class":286,"line":287},[284,1422,1423],{"class":846},"{\n",[284,1425,1426,1430,1433,1436,1438],{"class":286,"line":298},[284,1427,1429],{"class":1428},"s39Yj","    \"",[284,1431,458],{"class":1432},"sseR_",[284,1434,1435],{"class":1428},"\"",[284,1437,1370],{"class":846},[284,1439,1440],{"class":846}," {\n",[284,1442,1443,1446,1450,1452,1454],{"class":286,"line":310},[284,1444,1445],{"class":1428},"        \"",[284,1447,1449],{"class":1448},"sZMiF","allow-plugins",[284,1451,1435],{"class":1428},[284,1453,1370],{"class":846},[284,1455,1440],{"class":846},[284,1457,1458,1461,1463,1465,1467],{"class":286,"line":1305},[284,1459,1460],{"class":1428},"            \"",[284,1462,438],{"class":1244},[284,1464,1435],{"class":1428},[284,1466,1370],{"class":846},[284,1468,1469],{"class":1428}," true\n",[284,1471,1473],{"class":286,"line":1472},5,[284,1474,1475],{"class":846},"        }\n",[284,1477,1479],{"class":286,"line":1478},6,[284,1480,1481],{"class":846},"    }\n",[284,1483,1485],{"class":286,"line":1484},7,[284,1486,1487],{"class":846},"}\n",[244,1489,1490,1492,1493,1495,1496,1499,1500,1503,1504,1507],{},[268,1491,1338],{}," помечен в исходниках ",[268,1494,438],{}," как ",[268,1497,1498],{},"deprecated, remove on the major release",", поэтому в ",[268,1501,1502],{},"require"," стоит потолок\n",[268,1505,1506],{},"\"composer\u002Finstallers\": \"^1.0 || ^2.0\"",". Снимут потолок — модуль уедет в чужой\nкаталог.",[264,1509,92],{"id":1510},"проверка-на-портале",[244,1512,1513,1514,1517,1518,1521,1522,262],{},"Каталог модуля браузеру недоступен: в поставке nginx стоит ",[268,1515,1516],{},"deny all"," на\n",[268,1519,1520],{},"^\u002Fbitrix\u002F(modules|local_cache|stack_cache|managed_cache|php_interface)",".\nПоэтому фронт и раскладывается в ",[268,1523,1524],{},"\u002Fbitrix\u002Fjs",[244,1526,1527],{},"Проверить на стенде:",[276,1529,1534],{"className":1530,"code":1532,"language":1533},[1531],"language-text","\u002Fbitrix\u002Fmodules\u002Fshef.problems\u002Finstall\u002Fjs\u002Fshef-problems\u002Fmonolog-pr-html\u002Fstyle.css  -> 403\n\u002Fbitrix\u002Fjs\u002Fshef-problems\u002Fmonolog-pr-html\u002Fstyle.css                              -> 200\n\u002Fbitrix\u002Fadmin\u002Fshef_problems_logs.php                                            -> страница логов, только администратору\n","text",[268,1535,1532],{"__ignoreMap":105},[244,1537,1538,1539,262],{},"Логи лежат вне корня сайта, ссылки на них нет вовсе — см.\n",[248,1540,1541],{"href":145},"security.md",[244,1543,1544,1545,1548],{},"Полная процедура проверки на портале — в ",[248,1546,1547],{"href":176},"portal-check.md",":\nшаги с ожидаемым результатом, отдельно обновление с 1.x и запуск\nпримеров на живом ядре. Тестами рантайм Битрикса не покрыть, поэтому эта\nпроцедура и есть тест.",[1550,1551,1552],"note",{},[244,1553,1554,1555,1560],{},"Источник: ",[248,1556,1559],{"href":1557,"rel":1558},"https:\u002F\u002Fgithub.com\u002Fbx-shef\u002Fproblems\u002Fblob\u002Fmain\u002Fdocs\u002Fbuild-and-install.md",[256],"problems\u002Fdocs\u002Fbuild-and-install.md"," — правки туда, сайт пересобирается сам.",[1562,1563,1564],"style",{},"html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sseR_, html code.shiki .sseR_{--shiki-light:#9C3EDA;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":105,"searchDepth":310,"depth":310,"links":1566},[1567,1569,1570,1574,1575,1579,1582,1583,1584],{"id":266,"depth":298,"text":1568},"build.sh — точка входа сборки и проверок",{"id":329,"depth":298,"text":330},{"id":473,"depth":298,"text":474,"children":1571},[1572,1573],{"id":648,"depth":310,"text":649},{"id":713,"depth":310,"text":714},{"id":753,"depth":298,"text":754},{"id":906,"depth":298,"text":907,"children":1576},[1577],{"id":1058,"depth":310,"text":1578},"Что в ci.yml выглядит ошибкой, но ею не является",{"id":1106,"depth":298,"text":1107,"children":1580},[1581],{"id":1170,"depth":310,"text":1171},{"id":1202,"depth":298,"text":1203},{"id":1325,"depth":298,"text":1326},{"id":1510,"depth":298,"text":92},"md",{},true,"---\ntitle: \"Сборка, CI и релиз\"\n---\n\nРаскладка репозитория — в [module-structure.md](\u002Fmodules\u002Fproblems\u002Fmodule-structure), процесс —\nв [CONTRIBUTING.md](https:\u002F\u002Fgithub.com\u002Fbx-shef\u002Fproblems\u002Fblob\u002Fmain\u002FCONTRIBUTING.md), установка глазами пользователя —\nв [README.md](\u002Fmodules\u002Fproblems).\n\n## `build.sh` — точка входа сборки и проверок\n\nВторая проверка — линтер, отдельной целью Composer: см. раздел ниже.\n\n```bash\n.\u002Fbuild.sh            # проверки + архив shef.problems.zip\n.\u002Fbuild.sh --check    # только проверки\n.\u002Fbuild.sh --version  # напечатать версию модуля\n```\n\nCI зовёт **её же**. Это не украшение: если бы сервер гонял свой набор команд,\nлокальный зелёный прогон и серверный красный означали бы разные вещи, и\nразбираться пришлось бы в двух местах сразу.\n\n## Линтер — рядом со сборкой, а не внутри\n\n```bash\ncomposer install         # один раз: инструменты разработчика в vendor-dev\u002F\ncomposer run lint        # сухой прогон: покажет диф и упадёт\ncomposer run lint:fix    # привести файлы\n```\n\nphp-cs-fixer, набор `@PSR12` целиком, правила — `.php-cs-fixer.dist.php`.\nВерсия инструмента пришпилена точно (`3.95.27`, без `^`): набор `@PSR12`\nпополняется в минорных выпусках, и с `^3.0` CI однажды покраснел бы на\nкоммите, который ничего не менял. Его зависимости держит `composer.lock` —\nон под git, вопреки обычаю для библиотек; `config.platform.php` = 8.2.0,\nиначе lock разрешился бы под PHP того, кто его собирал, и на 8.2 не встал бы.\n\nИз `build.sh` линтер не зовётся: сборке хватает php, git и zip, и она обязана\nотрабатывать в свежем клоне. Позови она линтер — `.\u002Fbuild.sh --check` зависел\nбы от сети.\n\n**`vendor-dev\u002F`, а не `vendor\u002F`.** `vendor\u002F` здесь — своя копия Monolog под\ngit, она едет в поставку. Поставь Composer инструменты туда, `composer install`\nпереписал бы копию той версией Monolog, что разрешилась в lock. Плагин\n`composer\u002Finstallers` в корневом `composer.json` выключен (`false`): иначе он\nразложил бы `bxshef\u002Foptions` в `bitrix\u002Fmodules\u002F` посреди репозитория.\nПотребителей пакета это не касается — `config` Composer читает только у\nкорневого проекта.\n\nЛинтер правит PHP-токены. Инлайновый HTML между `?>` и `\u003C?php` он не трогает —\nего раскладка доводится руками; содержимое строковых литералов не трогает ни\nон, ни рука: это данные программы.\n\n## Что проверяется\n\n| проверка | что ловит |\n|---|---|\n| `check_filenames` | имя, с которым не справится скрипт; символическую ссылку под контролем git |\n| `check_lists` | файл, не попавший ни в SHIP, ни в KEEP |\n| `check_gitattributes` | расхождение KEEP и `export-ignore` — в обе стороны |\n| `check_encoding` | файл не в UTF-8, BOM в начале файла |\n| `check_php` | `php -l` по всем PHP |\n| `check_short_tags` | короткий тег `\u003C?` |\n| `check_js` | `node --check` по всем JS |\n| `check_lowercase` | заглавные буквы в путях `lib\u002F` |\n| `check_version` | пустой или кривой `VERSION`, пустой `VERSION_DATE` |\n| `run_tests` | `tests\u002F*_test.php` (php) и `tests\u002F*_test.mjs` (node) |\n| `check_composer_package` | состав `git archive` разошёлся со списком SHIP |\n\nПри сборке дополнительно: состав zip сверяется со списком SHIP, а первый\nуровень внутри архива — с `shef.problems\u002F`.\n\n### Что здесь сделано «строже, чем хотелось»\n\n**Пропущенная проверка выглядит как пройденная.** Поэтому:\n\n* нет `node`, а JS или `*_test.mjs` в репозитории есть — отказ, а не примечание;\n* `run_tests` сначала считает, сколько тестов ЕСТЬ, и сверяет с числом\n  прогнанных: переименованный файл или тест в подкаталоге иначе выпал бы из\n  прогона молча;\n* `check_composer_package` сверяет **индекс** (`git write-tree`), а не `HEAD`, и\n  потому работает и на грязном дереве. Раньше он на ней пропускался — а локально\n  дерево грязное почти всегда, так что единственная проверка, ловящая\n  расхождения самого `git archive`, срабатывала только в CI.\n\n**Символическая ссылка разводит каналы поставки молча.** `cp` в архив\nразыменовывает её и кладёт содержимое цели, `git archive` кладёт саму ссылку, а\nобе сверки состава при этом зелёные — они сверяют имена. Поэтому ссылок под\nконтролем git просто не бывает: `check_filenames` роняет сборку.\n\n### Про короткие теги отдельно\n\n`php -l` их **не ловит**. При `short_open_tag=Off` — а это значение по\nумолчанию — `\u003C?` открывающим тегом не считается, и файл целиком становится\nинлайновым HTML. Синтаксически он остаётся правильным, в нём просто нет PHP.\nА на портале классы из такого файла не определяются, зато исходник уезжает в\nбраузер.\n\nПоэтому `check_short_tags` спрашивает сам PHP через `token_get_all()`, а не\ngrep: `\u003C?` внутри строки или комментария лежит в своём токене, опасный же\nостаётся куском `T_INLINE_HTML`. Наивный grep краснел бы на каждом регулярном\nвыражении вида `\u002F\u003C?\u002F`.\n\n## Архив\n\nАрхив содержит **каталог модуля целиком**: первым уровнем внутри zip лежит\n`shef.problems\u002F`, иначе при распаковке файлы рассыплются прямо по\n`bitrix\u002Fmodules\u002F`. Проверяется в самом скрипте, а не глазами.\n\nСверить поставку двумя путями установки можно так:\n\n```bash\ngit archive --format=tar \"$(git write-tree)\" | tar -tf - | grep -v '\u002F$' | sort > \u002Ftmp\u002Fcomposer.txt\n.\u002Fbuild.sh && unzip -Z1 shef.problems.zip | grep -v '\u002F$' | sed 's#^shef.problems\u002F##' | sort > \u002Ftmp\u002Fzip.txt\ndiff \u002Ftmp\u002Fcomposer.txt \u002Ftmp\u002Fzip.txt    # должно быть пусто\n```\n\n## CI\n\n`.github\u002Fworkflows\u002Fci.yml`, шесть задач:\n\n| задача | что делает |\n|---|---|\n| `PHP 8.2` … `PHP 8.5` | `.\u002Fbuild.sh --check`, `fail-fast: false` |\n| `Composer` | `composer validate --strict`: пакет ставят через Composer, и сломанный манифест виден только тому, кто ставит; заодно свежесть `composer.lock` |\n| `Skills` | `sync.sh --check` против `MANIFEST` источника в `bx-shef\u002Foptions`: навыки здесь — копия, и копия не должна отставать |\n| `Build` | `.\u002Fbuild.sh` плюс архив артефактом прогона |\n| `Lint` | `composer install` и `composer run lint`, одна версия PHP — 8.2 |\n| `CI` | ворота, `needs: [checks, composer, skills, build, lint]` |\n\n**`Skills` краснеет, когда навыки поправили в shef.options.** Это не поломка\nэтого репозитория, а сигнал: разложите навыки заново\n(`..\u002Foptions\u002F.claude\u002Fskills\u002Fsync.sh --to .`) и закоммитьте. Копию на месте не\nправят — правка будет затёрта следующей раскладкой.\n\n**Версии PHP в матрице — не только про код модуля.** `tests\u002Fvendor_test.php`\nразбирает все файлы своей копии Monolog с `error_reporting=-1`: новая версия PHP\nс новыми deprecation покраснеет здесь, а не в логе портала. Так уже было:\nMonolog 3.3.1 на PHP 8.4 сыпал deprecation из `Monolog\\Logger`.\n\nВ защите ветки требуется ровно одна проверка — `CI`. Остальные её зависимости,\nпоэтому новая задача не потребует правки ruleset.\n\n### Что в `ci.yml` выглядит ошибкой, но ею не является\n\n**`if: always()` у задачи `CI`** — обязателен вместе с явной сверкой результатов\nзависимостей. Без него задача была бы *пропущена* при падении зависимости, а\nпропущенную проверку защита ветки засчитывает как *пройденную*: красный CI уехал\nбы в `main`.\n\nПодмывает заменить на `!cancelled()` — не надо. Тогда отменённый прогон стал бы\nдавать пропущенную проверку, и, отменив прогон вручную, можно было бы смержить\nнепроверенное.\n\n**Вытесненный по `concurrency` прогон краснеет** на устаревшем коммите. Это шум,\nа не поломка: защита смотрит на проверки головного коммита.\n\n## Релиз\n\n`.github\u002Fworkflows\u002Frelease.yml`, два входа.\n\n**Пуш тега `v*`** — тег **сверяется** с `VERSION` из `install\u002Fversion.php`.\nРасхождение роняет прогон: тегу не доверяем, иначе на портал уедет архив,\nверсия которого врёт.\n\n**`workflow_dispatch` от `main`** — тег **выводится** из `VERSION` и ставится\nсам. Запуск от другой ветки отклоняется, занятый тег ловится до сборки.\n\nВторой вход обязателен: пуш тегов бывает недоступен — другие права, прокси\nсессии, — а релиз выпускать надо.\n\n**Тег ставится после успешной сборки.** Поставленный раньше, он пережил бы\nупавшую сборку, и следующая попытка упёрлась бы в занятый тег.\n\nПримечания к релизу собираются из секции `## \u003Cверсия>` в `CHANGELOG.md`.\n\n### Packagist\n\nПоследним шагом релиз дёргает `update-package`. Без секретов\n`PACKAGIST_USERNAME` и `PACKAGIST_TOKEN` шаг пропускается, и релиз при этом\n**не падает**: невыложенный релиз чинить нечем, а отставший Packagist\nдогоняется кнопкой Update за десять секунд.\n\nЭндпойнт умеет только **обновлять уже зарегистрированный** пакет. Первую\nрегистрацию делают один раз руками: packagist.org → Submit →\n`https:\u002F\u002Fgithub.com\u002Fbx-shef\u002Fproblems`.\n\n## Monolog: Composer и своя копия\n\n`composer.json` требует `monolog\u002Fmonolog` — через Composer он ложится в vendor\nпроекта. Архив несёт свою копию в `vendor\u002Fmonolog\u002Fmonolog` (SHIP). Какую\nподключать, решает `.settings.php` при каждой загрузке, см.\n[Monolog](\u002Fmodules\u002Fproblems\u002Fmonolog).\n\nОбновить свою копию:\n\n```bash\ngit clone --depth 1 --branch \u003Cверсия> https:\u002F\u002Fgithub.com\u002FSeldaek\u002Fmonolog.git \u002Ftmp\u002Fmonolog\nrm -rf vendor\u002Fmonolog\u002Fmonolog && mkdir -p vendor\u002Fmonolog\u002Fmonolog\ncp -a \u002Ftmp\u002Fmonolog\u002F{src,LICENSE,README.md,CHANGELOG.md,composer.json} vendor\u002Fmonolog\u002Fmonolog\u002F\n.\u002Fbuild.sh --check\n```\n\nВерсия копии обязана подходить под ограничение в `composer.json` — иначе\nпоставленный архивом и поставленный Composer модуль работали бы на разном\nMonolog. Сторожит `tests\u002Fvendor_test.php`, версию он читает из первой записи\n`CHANGELOG.md` копии.\n\n## Куда Composer кладёт модуль\n\n`composer.json`: `type` = `bitrix-module` плюс\n`extra.installer-name = shef.problems`. Тогда Composer разворачивает модуль в\n`bitrix\u002Fmodules\u002Fshef.problems\u002F` без настройки на стороне потребителя:\n`installer-name` читается из пакета, а `{$bitrix_dir}` — только из корневого\n`composer.json`, повлиять на него пакет не может.\n\n**`bitrix-d7-module` развернул бы модуль не туда.** Шаблоны в\n`composer\u002Finstallers`:\n\n* `bitrix-module` → `{$bitrix_dir}\u002Fmodules\u002F{$name}\u002F`\n* `bitrix-d7-module` → `{$bitrix_dir}\u002Fmodules\u002F{$vendor}.{$name}\u002F`\n\nа `installer-name` подменяет только `{$name}`. Для пакета `bxshef\u002Fproblems`\nвторой вариант дал бы `bitrix\u002Fmodules\u002Fbxshef.shef.problems\u002F` — каталог, которого\nБитрикс не знает.\n\nНа стороне проекта-потребителя Composer 2.2+ требует явного разрешения\nплагина, иначе в неинтерактивном режиме (CI) он не отработает и пакет ляжет в\n`vendor\u002Fbxshef\u002Fproblems`:\n\n```json\n{\n\t\"config\": {\n\t\t\"allow-plugins\": {\n\t\t\t\"composer\u002Finstallers\": true\n\t\t}\n\t}\n}\n```\n\n`bitrix-module` помечен в исходниках `composer\u002Finstallers` как `deprecated,\nremove on the major release`, поэтому в `require` стоит потолок\n`\"composer\u002Finstallers\": \"^1.0 || ^2.0\"`. Снимут потолок — модуль уедет в чужой\nкаталог.\n\n## Проверка на портале\n\nКаталог модуля браузеру недоступен: в поставке nginx стоит `deny all` на\n`^\u002Fbitrix\u002F(modules|local_cache|stack_cache|managed_cache|php_interface)`.\nПоэтому фронт и раскладывается в `\u002Fbitrix\u002Fjs`.\n\nПроверить на стенде:\n\n```\n\u002Fbitrix\u002Fmodules\u002Fshef.problems\u002Finstall\u002Fjs\u002Fshef-problems\u002Fmonolog-pr-html\u002Fstyle.css  -> 403\n\u002Fbitrix\u002Fjs\u002Fshef-problems\u002Fmonolog-pr-html\u002Fstyle.css                              -> 200\n\u002Fbitrix\u002Fadmin\u002Fshef_problems_logs.php                                            -> страница логов, только администратору\n```\n\nЛоги лежат вне корня сайта, ссылки на них нет вовсе — см.\n[security.md](\u002Fmodules\u002Fproblems\u002Fsecurity).\n\nПолная процедура проверки на портале — в [portal-check.md](\u002Fmodules\u002Fproblems\u002Fportal-check):\nшаги с ожидаемым результатом, отдельно обновление с 1.x и запуск\nпримеров на живом ядре. Тестами рантайм Битрикса не покрыть, поэтому эта\nпроцедура и есть тест.\n\n::note\nИсточник: [problems\u002Fdocs\u002Fbuild-and-install.md](https:\u002F\u002Fgithub.com\u002Fbx-shef\u002Fproblems\u002Fblob\u002Fmain\u002Fdocs\u002Fbuild-and-install.md) — правки туда, сайт пересобирается сам.\n::\n\n",{"title":82,"description":85},"VLMs3gvIDinnUYC4PG0tca1JnHFOaj5s8Z46jThbzh4",[1592,1593],{"title":128,"path":167,"stem":168,"description":105,"children":-1},{"title":87,"path":173,"stem":174,"description":90,"children":-1},1790857269980]