
→ Установить с VS Code Marketplace
Нишевый add-on поверх vscode-web (браузерный code-server,
репозиторий gitlab:prj/vscode-web.git), не часть его базового
функционала. Решает
конкретную боль: разработчики Frappe-стендов либо роняют bench
настолько, что проще пересобрать с нуля, либо приходят в команду без
bench вообще, либо тратят время на ручную установку/обновление
внутренних приложений команды. Ручные bench init/bench new-site/
bench get-app с правильными флагами (свой форк Frappe, пароли, доп.
приложения, ветки) — источник ошибок копипаста. Расширение оборачивает
штатный bench CLI в UI внутри VS Code. Системные зависимости
(Postgres, Redis, Python, Node) считаются уже установленными на
стенде — вне ответственности расширения.
Иконка расширения (images/icon.png/icon.svg, marketplace-иконка) —
официальный текущий бренд-марк Frappe Framework (буква "F"), взят как
есть из frappe/public/images/frappe-favicon.svg в самом фреймворке
(frappe/frappe, без переработки).
Иконка Activity Bar (images/activitybar.svg) — простая оригинальная
монохромная пиктограмма (workbench), не логотип. Расширение не
аффилировано с Frappe Technologies — сходство иконки с официальным
брендом Frappe не означает официальной поддержки или одобрения со
стороны Frappe Technologies.
UI: две точки входа
Иконка в Activity Bar ("Frappe Bench") открывает единственную
докнутую панель — "Приложения": список репозиториев из GitLab-
группы команды (см. §"Приложения из GitLab" ниже). Ничего, кроме
этого списка, в сайдбаре нет осознанно — формы с полями (репо, ветка,
пароли, домен) тесны в узкой боковой колонке, для них есть отдельная
широкая панель.
Широкая панель с тремя вкладками — Установка | Диагностика |
Настройки — открывается:
- кнопкой (иконка "окно") в шапке панели "Приложения";
- командами Command Palette (
Ctrl+Shift+P): Frappe Bench: Развернуть новый bench, Frappe Bench: Диагностика, Frappe Bench: Настройки.
Это singleton WebviewPanel в области редактора (не в сайдбаре):
повторное открытие поднимает уже существующую панель и переключает
вкладку, а не плодит новые окна. Панель не закрывается сама после
запуска развёртывания — прогресс идёт в отдельном терминале (VS Code
Task), а форма остаётся на месте.
Вкладка "Установка"
Путь, форк/ветка Frappe, имя сайта, доп. приложения из настроек
(чекбоксы), пароли — всё видно и редактируется сразу, без цепочки
модальных промптов. По умолчанию имя инстанса — <unix-логин>_sandbox,
путь — /var/www/html/<unix-логин>_sandbox (frappeBench.benchesRoot),
по принятой в команде конвенции; путь под курсором проверяется на лету
(свободен / уже пуст и переиспользуется / занят / там уже есть bench).
Если путь занят — форма требует явно отметить "Снести и развернуть
заново", иначе кнопка "Развернуть" остаётся неактивной; старая
директория при этом переименовывается (<path>.old-<timestamp>),
не удаляется. Весь вывод скрипта дублируется в
~/frappe-bench-tools-logs/<имя bench>-<timestamp>.log (0600,
переживает сам скрипт — тот удаляет только себя) — терминал VS Code
свой буфер на диск не пишет, без файла разбирать проблему после
закрытия панели/перезапуска стенда было бы не по чему. По подтверждению
собирается один bash-скрипт (бэкап старого при конфликте → sudo install -d под целевую директорию → bench init ... --dev → bench get-app×N → bench new-site → симлинк $HOME/<имя> → опционально
nginx, см. ниже) и запускается как VS Code Task (виден в терминале
целиком, ничего не скрыто в фоновом процессе). /var/www/html обычно
не в собственности разработчика — создание/пересоздание поддиректории
идёт через sudo (спросит пароль прямо в терминале, если нужно);
дальше bench выполняется уже без sudo — сам bench отказывается
работать от root.
Есть необязательное поле "Домен для nginx" — если заполнено:
bench config dns_multitenant on — без этого bench setup nginx
полностью игнорирует домены, добавленные через add-domain (сборка
server_name для дополнительных доменов в bench/config/nginx.py
идёт только в этой ветке) — запрос на домен молча падал в
default-сайт nginx вместо нашего server-блока (поймано на живом
прогоне).
bench setup add-domain → bench setup nginx --yes --log_format none (без --log_format none bench подставляет свой дефолт main
в access_log, а log_format main нигде не объявлен в дефолтном
nginx на Debian/Ubuntu — nginx -t падает с "unknown log format
"main""; тоже поймано на живом прогоне).
- Сканирование
/etc/nginx/conf.d/*.conf и /etc/nginx/sites-enabled/*
на файлы, уже упоминающие этот домен (типичная причина — остаток
ручного эксперимента до автоматизации, встречалось на практике: он
один способен ронять nginx -t, независимо от того, что мы сами
генерируем корректно) — такие файлы переносятся в
<файл>.old-<timestamp> (не удаляются).
- Подключение сгенерированного
config/nginx.conf в
/etc/nginx/conf.d/auto_<имя bench>.conf (префикс auto_ — чтобы
сразу отличать конфиги этого расширения от ручных/легаси при поиске
и чистке; уникальное имя на bench, не общий nginx.conf, чтобы не
конфликтовать с другими пользователями стенда) → sudo nginx -t →
reload только при успешном тесте (при ошибке симлинк снимается
обратно, общий nginx не трогается — тот же принцип, что у
vscode-web).
- Если установлен
supervisorctl (проверяется явно — если нет,
печатается понятный WARN с командой установки, остальная часть
скрипта не падает): сперва проверка владельца процесса на
каждом из 4 наших портов (ss -ltnp → PID → ps -o user=) — если
порт уже занят, но процесс принадлежит другому Unix-
пользователю, скрипт немедленно останавливается с ошибкой и ничего
не трогает (не наш namespace, не наша территория). Если процесс
свой же (старый namespace этого пользователя, например
переустановка поверх уже существующего своего bench) — не страшно,
restart ниже его корректно заменит. Проверка построена на sudo ss ... | grep ... | head -1 || true — без || true set -o pipefail
валит весь скрипт молча на первом же свободном порту (grep без
совпадения возвращает 1), это штатный, а не ошибочный исход
(поймано на живом прогоне: скрипт обрывался без единой строки
ошибки в логе). Дальше: bench setup supervisor --yes → подключение
config/supervisor.conf в /etc/supervisor/conf.d/auto_<имя bench>.conf → supervisorctl reread && update → явный
supervisorctl restart <имя>-web: <имя>-workers: <имя>-redis:.
Без домена никто бы вообще не слушал выделенные порты; но и с
доменом одного update недостаточно на переустановке — update
сравнивает только командную строку процессов в самом
supervisor.conf (она не меняется, redis-server всегда
запускается одной и той же командой), не содержимое файлов
(config/redis_{cache,queue}.conf), которые эти процессы читают
при своём старте. Уже запущенный со старой переустановки
redis-server не заметит, что порт в файле сменился, без явного
restart (поймано на живом прогоне: сайт логинился, но Redis слушал
старый порт). Штатный bench restart тут не спасает — проверено в
исходниках: он рестартует -web:/-workers:, но никогда
-redis:.
Порты — по UID, детерминированно (src/benchPorts.ts, тот же
принцип, что vscode-web уже применяет для code-server —
20000+uid): webserver_port/socketio_port/redis_cache/
redis_queue у bench по умолчанию одинаковы для любого bench на
машине (8000/9000/13000/11000) — на живом прогоне с
несколькими bench на одном стенде это гарантированный конфликт,
поймано вживую. Расширение выставляет все четыре через bench set-config -g сразу после bench init (плюс bench setup redis,
чтобы перегенерировать config/redis_{cache,queue}.conf под новые
порты — bench setup supervisor сам их не создаёт, только ссылается
на уже лежащие файлы) — детерминированно от UID разработчика: разные
пользователи никогда не пересекутся, а переустановка своего же
bench каждый раз попадает на те же порты.
webserver_port/socketio_port выставляются через bench set-config -g -p — -p/--parse обязателен. Без него bench пишет значение
в common_site_config.json строкой, а не числом; и это не только про
наш собственный bench — bench init у любого другого bench в том
же benchesRoot сканирует порты всех соседних bench
(make_ports() в bench/config/common_site_config.py), чтобы
подобрать свободный следующий, и падает с TypeError: '>' not supported between instances of 'str' and 'int', если наткнётся на
смешанный список int/str. Поймано вживую ровно так: bench, развёрнутый
без -p, спокойно встал сам, но сломал bench init у следующего
разработчика, попытавшегося развернуть свой bench на том же
shared-стенде.
Форма запоминает значения между запусками (форк/ветка Frappe,
выбранные доп. приложения, симлинк, домен для nginx, путь последнего
развёрнутого bench) через context.globalState — кроме паролей,
те принципиально нигде не сохраняются, вводятся заново каждый раз.
Вкладка "Диагностика"
Read-only проверки структуры bench, доступности Postgres/Redis,
git-состояния apps/frappe, наличия и корректности симлинка в
$HOME — результат рендерится прямо во вкладке. Путь к bench
подставляется автоматически: открытая workspace-папка, если это сам
bench → иначе последний развёрнутый через "Установку" путь → иначе
<benchesRoot>/<unix-логин>_sandbox по конвенции (та же цепочка
фолбэков используется и панелью "Приложения", detectCurrentBenchPath()
в src/benchFs.ts). При структурной поломке предлагает "Пересобрать
с нуля": старая директория переименовывается
(<path>.broken-<timestamp>), не удаляется, и открывается вкладка
"Установка" с предзаполненным именем.
Вкладка "Настройки"
GitLab Personal Access Token и URL группы, где живут приложения
команды — см. следующий раздел. Чек-лист сверху вкладки сразу
показывает, что уже настроено, а чего не хватает (актуально при
первом запуске). PAT — единственный секрет, который расширение вообще
хранит: не в settings.json, а в context.secrets (шифруется ОС),
никогда не отображается обратно в UI (только флаг "сохранён/не
сохранён").
Приложения из GitLab
Панель "Приложения" (Activity Bar) показывает репозитории из
настроенной GitLab-группы (frappeBench.gitlabAppsGroupUrl +
PAT из вкладки "Настройки") — GET /groups/:group/projects через
PRIVATE-TOKEN. Пока PAT/URL не заданы — панель показывает
viewsWelcome со ссылкой на "Настройки" вместо пустого списка.
Клик по приложению открывает панель деталей справа
(ViewColumn.Beside) — единственный переиспользуемый webview
(повторный клик по другому приложению просто обновляет его
содержимое, не плодит окна). Никаких hover/context-menu иконок прямо
на строке дерева — они оказались неочевидны на живом
UX-тестировании. В панели деталей — выпадающий список веток
(подтягивается через GET /projects/:id/repository/branches, тем же
PAT; если запрос не успел или не удался — тихий фолбэк на
default_branch) и контекстные кнопки:
- не установлено → "Установить" (
bench get-app + bench install-app) и "Восстановить из архива";
- установлено → "Переустановить" (
bench get-app --overwrite,
штатная фича самого bench — никакого ручного git pull/bench build/bench migrate) и "Удалить".
Обе install/update-команды идут без --site — default_site уже
прописан в common_site_config.json (провижининг всегда передаёт
--set-default), bench сам его резолвит тем же механизмом, каким
пользуется сам CLI.
Репозитории группы обычно приватные — PAT встраивается прямо в URL
клонирования (https://oauth2:<pat>@host/..., GitLab проверяет пароль
как PAT независимо от имени пользователя в URL). Это создаёт риск
утечки: сам bench печатает полную команду $ git clone https://oauth2:<pat>@... в свой собственный вывод при каждом
клонировании. Поэтому весь вывод любого сгенерированного скрипта идёт
через redaction-фильтр (sed -E 's#://[^/@[:space:]]+@#://REDACTED@#g',
scriptHeader() в src/scriptRunner.ts) до записи и на терминал,
и в постоянный лог-файл — токен никогда не оседает открытым текстом ни
там, ни там (проверено эмпирически: симуляция реального вывода
git clone/ошибки авторизации через сгенерированный скрипт — в
итоговом логе только REDACTED).
"Удалить" — bench uninstall-app <name> --yes (снять с сайта,
сайт бэкапится по умолчанию) → bench remove-app <name> (убрать код
из apps/). Модальное подтверждение перед запуском. Код не
удаляется безвозвратно — bench архивирует его в
archived/apps/<name>-<date>[_N] (штатный механизм самого remove()
в исходниках bench, тот же самый, что срабатывает и при bench get-app --overwrite).
"Восстановить из архива" — решает конкретный живой сценарий:
bench get-app --overwrite сначала архивирует старую копию
(apps/<name> → archived/apps/<name>-<date>), и только потом
клонирует заново; если клонирование падает (сеть, авторизация, не та
ветка) — приложение полностью пропадает из apps/, хотя код цел и
лежит в архиве. Кнопка показывает QuickPick по всем каталогам
archived/apps/<name>-* (сортировка по времени изменения, новые
сверху) — выбор восстанавливает архив в apps/<name> (cp -a) и
перезапускает bench restart. Останавливается с ошибкой, если
apps/<name> уже занято, вместо тихой перезаписи.
Защита от повторного клика: кнопки в панели деталей блокируются
сразу на клике (клиентский JS, ждём bench get-app/uninstall-app —
десятки секунд), плюс серверный guard в AppsTreeProvider не даёт
запустить вторую операцию для того же приложения, пока первая ещё не
завершилась — на случай гонки (двойной клик до того, как кнопка
успела задизейблиться).
Настройки (frappeBench.*)
| Настройка |
Назначение |
frappeBench.frappeRepo |
URL репозитория Frappe (обычно — свой форк на GitLab) |
frappeBench.frappeBranch |
ветка Frappe |
frappeBench.benchesRoot |
базовая директория для новых bench (дефолт /var/www/html) |
frappeBench.defaultApps |
список {name, repo, branch} для мультивыбора при развёртывании |
frappeBench.pythonPath |
путь к Python |
frappeBench.dbRootUser |
root-пользователь Postgres |
frappeBench.createHomeSymlink |
создавать ли $HOME/<имя bench> → <benchesRoot>/<имя bench> (дефолт true) |
frappeBench.gitlabAppsGroupUrl |
URL GitLab-группы с приложениями команды (например https://gitlab.example.com/team/apps) — для панели "Приложения" |
PAT для GitLab не настройка — вводится и хранится отдельно, во
вкладке "Настройки" (context.secrets), см. выше.
Пароли (root Postgres, Administrator) никогда не хранятся в
настройках — расширение спрашивает их при каждом запуске мастера
(type="password" в форме) и подставляет только во временный скрипт с
правами 0600, который удаляет сам себя сразу после выполнения. У
bench нет способа принять эти пароли иначе, чем аргументом командной
строки — они неизбежно ненадолго видны в ps aux во время выполнения,
это ограничение самого bench, не расширения.
Пример settings.json:
{
"frappeBench.frappeRepo": "git@gitlab.example.com:team/frappe.git",
"frappeBench.frappeBranch": "version-15-team",
"frappeBench.defaultApps": [
{ "name": "team_app", "repo": "git@gitlab.example.com:team/team_app.git", "branch": "develop" }
],
"frappeBench.gitlabAppsGroupUrl": "https://gitlab.example.com/team/apps"
}
Проверка обновлений (для code-server)
code-server (наш рантайм, vscode-web) умеет ставить расширения
только из open-vsx.org — это зашито в бинаре, официальный VS Code
Marketplace ему недоступен даже для внутреннего использования (Terms
of Use Microsoft). Публикация frappe-tools на open-vsx.org уперлась
в отдельный блокер (SSO-баг Eclipse Foundation при регистрации
publisher-аккаунта) — до его решения расширение для code-server
обновляется собственным механизмом, в обход Gallery API вообще.
Команда Frappe Bench: Проверить обновления
(src/updateCheck.ts) дёргает манифест на собственном сервисе
(updates.ide.undoo.ru, отдельный проект update-server, полная
спецификация протокола — в его README) и, если там версия новее
установленной, скачивает .vsix и ставит его через штатную
workbench.extensions.installExtension (эта команда официально
принимает Uri локального файла, не только id из маркета — отдельный
"маркет" реализовывать не пришлось). Та же проверка тихо выполняется
при активации расширения — недоступность сервиса при этом не
показывает пользователю ничего, кроме записи в Output-канал
"Frappe Bench" (сеть на shared-стенде не всегда стабильна, спам
ошибками при каждом старте IDE был бы хуже отсутствия автопроверки).
Id компонента на сервере (frappe-tools, путь
/v1/manifest/frappe-tools) — по договорённости совпадает с VS Code
extension id, но формально это независимые вещи: сервер ничего не
знает про publisher.name, id там — просто имя директории на хосте.
Публикация новой версии на этот сервис — ./maintain.sh publish (см.
ниже), не связана со сборкой самого .vsix.
Сборка
./maintain.sh build
# → dist/frappe-tools-<версия>.vsix
./maintain.sh help — список всех подкоманд (build, bump-version,
release).
Установка
Для обычного desktop VS Code — стандартная установка через вкладку
Extensions или ссылку на Marketplace вверху. Ниже — отдельный сценарий
именно для команды: code-server (браузерный vscode-web) обычно не
может ставить расширения напрямую из официального Marketplace
Microsoft (лицензионное ограничение, а не техническое), поэтому для
shared-стенда команды используется ручная установка .vsix.
Установка в code-server
Пакет не встроен в vscode-web и не разворачивается через
ide-enroll — это сознательно (нишевый add-on, не часть базового
функционала, см. §1 назначения выше). Устанавливать .vsix должен
сам разработчик под своим Unix-логином: в shared-режиме у
каждого пользователя свой code-server@<user>.service и своя
изолированная папка расширений (~/.local/share/code-server/ extensions) — установка от чужого имени (ctrl/root) на других не
подействует.
Проверенный способ — из шелла, с полным путём до бинаря.
В самом пакете vscode-web нет code-server в $PATH — на
пользовательские команды вынесены только ide-enroll/ide-unenroll/
ide-ctl (см. debian/vscode-web.install в репозитории vscode-web).
Вендоренный бинарь, тот же самый, что реально исполняется в
code-server@<user>.service, лежит по фиксированному внутреннему
пути:
/opt/vscode-web/code-server/bin/code-server --install-extension dist/frappe-tools-<версия>.vsix
Если где-то на стенде на $PATH случайно оказался другой
code-server (например, поставленный отдельно через npm/pip на
bench-стенде) — команда без полного пути либо не найдётся
(command not found), либо, что хуже, тихо установит расширение не
туда: в ~/.local/share/code-server того, другого инстанса, никак не
связанного с реально работающим code-server@<user>.service. Внешне
это выглядит как "ничего не произошло" — команда отрабатывает без
ошибок, а расширение просто не появляется там, где ты его ждёшь.
Полный путь до бинаря снимает эту неоднозначность.
Drag & drop .vsix работает не так, как в desktop VS Code, и на
редактор/вкладку бросать бесполезно (при тесте — вообще никакой
реакции). Причина не в vscode-web, а в самой архитектуре web-версии
VS Code: у desktop-приложения (Electron) файл из drop-события несёт
настоящий путь на диске, а у браузера — только blob в памяти без
пути; при обычном drop на редактор код-сервер пытается собрать
file:///extension-name.vsix и обратиться к "local extension
service", которого в браузере физически не существует ([подробнее —
обсуждение в coder/code-server
#4897](https://github.com/coder/code-server/discussions/4897)).
Правильная зона для DnD — боковая панель Explorer (дерево файлов),
не редактор: туда файл реально аплоадится на сервер как обычный файл,
и дальше через Command Palette → Extensions: Install from VSIX...
можно выбрать уже загруженный файл. Подтверждено вживую — сработало
с первой попытки. Оба способа (CLI с полным путём и DnD на Explorer)
рабочие; CLI чуть более предсказуем для скриптов/автоматизации, DnD —
быстрее руками.
Иконка Frappe Bench в Activity Bar и команды Frappe Bench: ... в
Command Palette появятся сразу после установки, без перезапуска
code-server.
Отладка
F5 в VS Code (открыв этот репозиторий как workspace) поднимает
Extension Development Host с уже загруженным расширением.