commands/
Декларативные определения команд для DWE-проекта.
Содержание
Заголовок раздела «Содержание»- Назначение
- Структура файлов и идентификаторы команд
- Структура файла
- Жизненный цикл выполнения
- Видимость, регистрация и обнаружение
- Сквозные примеры
- Связанные команды
- Интерактивный браузер
- Дополнительное чтение
Назначение
Заголовок раздела «Назначение»workspace/commands/ — дом для каждого переиспользуемого, скриптуемого действия, которое проект предоставляет через CLI: shell-доступ к контейнерам, шаги сборки, операции с базой данных, многошаговые сценарии, хуки деплоя, пользовательские скрипты и т. д.
Каждый YAML-файл объявляет одну или несколько именованных команд. Команды обнаруживаются автоматически обходом дерева каталогов, адресуются через точечный идентификатор и могут выполняться напрямую (dwe commands <id>) либо ссылаться из сценариев и пайплайнов (deploy.yml, lifecycle.yml).
Эта страница — точка входа в справочник по конфигурации. Директивы, типы, шаблонизация и валидация описаны на отдельных соседних страницах — см. Дополнительное чтение в конце.
Структура файлов и идентификаторы команд
Заголовок раздела «Структура файлов и идентификаторы команд»Структура каталогов определяет префикс группы, а имя файла без расширения определяет конечный сегмент. Имена файлов и подкаталогов полностью на усмотрение проекта — приведённые ниже названия иллюстративны, и допускается произвольное количество файлов любой вложенности.
workspace/commands/├── <top-group>.yml → group: <top-group>├── … → any number of top-level groups└── <parent-group>/ → optional subdirectory expands a group ├── <child>.yml → group: <parent-group>.<child> ├── <child>/ → optional deeper subdirectory │ └── <leaf>.yml → group: <parent-group>.<child>.<leaf> └── … → any number of children, any depthКонкретный проект может организовать всё, например, так — каждое имя здесь выбрано проектом, это не соглашение, навязываемое CLI:
workspace/commands/├── db.yml → group: db├── app.yml → group: app└── services/ ├── <service-a>.yml → group: services.<service-a> ├── <service-a>/ │ └── db.yml → group: services.<service-a>.db └── <service-b>.yml → group: services.<service-b>Полный идентификатор каждой команды — <group>.<name>, где <name> — ключ в карте commands: внутри файла.
Шаблон: размещайте основные команды группы в одном файле, названном по группе (services/<service>.yml), и разделяйте большие группы в соседний подкаталог только тогда, когда команд достаточно, чтобы оправдать логические подгруппы (services/<service>/db.yml, services/<service>/cache.yml). Подкаталог опционален — маленькие группы остаются в одном файле. Обязательных файлов нет: у проекта может быть ноль, одна или десятки групп на любой глубине.
# workspace/commands/db.yml → group "db"commands: cli: # full ID: db.cli type: service_exec ... dump-create: # full ID: db.dump-create type: script ...Зарезервированных имён файлов нет — каждый файл *.yml вносит сегмент, выводимый из его пути.
Структура файла
Заголовок раздела «Структура файла»У каждого файла два ключа верхнего уровня: опциональный блок метаданных group: и обязательная карта commands:.
group: title: Database description: Database container management commands
commands: <local-name>: type: <type> description: <text> # ... directives below ...| Поле | Тип | Описание |
|---|---|---|
group.title | string | Отображаемый заголовок, показываемый в dwe commands list |
group.description | string | Короткое описание, отображаемое рядом с группой; как и у команды, там показывается только его первая непустая строка (см. Описание и краткая строка) |
group.hide | string | Опциональное выражение-условие; когда truthy, скрывает группу и каскадно — все её потомки (команды и подгруппы). См. Условие hide. |
commands | map | Именованные определения команд (ключ = локальное имя) |
Жизненный цикл выполнения
Заголовок раздела «Жизненный цикл выполнения»Каждая команда, независимо от типа, проходит один и тот же пайплайн выполнения:
flowchart TD
A[Разрешение параметров] --> B[Разрешение контекста]
B --> C[Вычисление путей файлов]
C --> D{Подтверждение?}
D -- да --> E[Запрос пользователя]
D -- нет --> F[Подготовка файловых эффектов]
E --> F
F --> G[Запуск раннера]
G --> H{Успех?}
H -- да --> I[Сообщение об успехе]
H -- нет --> J[Выполнение cleanup в порядке LIFO]
J --> K[Сообщение об ошибке]
Фазы:
-
Разрешение параметров — для каждого объявленного параметра пробуются по очереди: переданное значение →
default_from(точечный путь в объединённый конфиг; пустой результат считается отсутствием) → литеральныйdefault→ ошибка обязательности. Затем значение приводится к объявленному типу и проверяется наpattern. -
Разрешение контекста — каждый точечный путь
context.<key>.fromчитается из объединённого конфига.dwe validateпредупреждает (доменcommands), когда один из этих dot-путей не резолвится в собранном конфиге. Последствия у полей разные: неразрешимыйparams.<name>.default_fromуходит вdefault:либо валит параметр, если тотrequired:и другого значения нет; неразрешимыйparams.<name>.options.fromдаёт пустой список опций, и форма выбора не предлагает ничего; неразрешимыйcontext.<name>.fromрендерится пустой строкой.dwe validate --strictпревращает предупреждение в ошибку. -
Вычисление путей файлов — рендерятся шаблоны
path/candidates, пути нормализуются в абсолютные, выполняется поиск файлов. Без побочных эффектов. -
Подтверждение — при
confirmation: trueзапрашивается подтверждение пользователя; запрос обходится только черезSkipConfirm(устанавливается флагом--yes/-yи наследуется дочерними шагами сценария). Иначе диспетчеризация по stdin: TTY →huh.Confirm, не-TTY → простой fallback Y/n, который автоматически отвечает «yes», когда переменная окруженияCIустановлена (в любое непустое значение). Отказ прерывает команду. Полное дерево решений см. в Поток подтверждения. -
Подготовка файловых эффектов —
mkdir, проверкиoverwrite, регистрация cleanup-колбэков. -
Запуск — диспетчеризация в раннер, специфичный для типа (host shell, DWE CLI, контейнерные exec/run, скрипт или сценарий).
-
Успех / ошибка — выводится
messages.successилиmessages.error. При ошибке зарегистрированные cleanup-колбэки срабатывают в порядке LIFO до сообщения об ошибке.
Видимость, регистрация и обнаружение
Заголовок раздела «Видимость, регистрация и обнаружение»- Файлы под
workspace/commands/рекурсивно обнаруживаются на старте. - Каждый файл парсится и валидируется; сбой загрузки останавливает запуск со структурированной ошибкой, указывающей на файл и поле.
private: trueскрывает команды изdwe commands listи отвергает прямой вызов черезdwe commands. На приватные команды по-прежнему можно ссылаться из сценариев и пайплайнов — полезно для шагов, которые должны запускаться только как часть более крупной последовательности.
db.up: type: dwe private: true # used only inside db.start workflow cmd: "docker up db"Сквозные примеры
Заголовок раздела «Сквозные примеры»Самодостаточная команда service_exec
Заголовок раздела «Самодостаточная команда service_exec»db.create: type: service_exec description: Create a database in the db container service: db params: database: type: string required: true pattern: ^[a-zA-Z0-9_-]+$ env: MYSQL_PWD: "${vars.db.password}" messages: success: "Database `${param.database}` is ready." error: "Failed to create database `${param.database}`." cmd: "mariadb -u${vars.db.user} -e 'CREATE DATABASE IF NOT EXISTS `${param.database}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'"Команда script с файловыми артефактами
Заголовок раздела «Команда script с файловыми артефактами»db.dump-create: type: script description: Create a database dump file params: database: type: string default_from: vars.db.database pattern: ^[a-zA-Z0-9_-]+$ dump_dir: type: string default_from: vars.db.backup_dir required: true pattern: ^[^*?\[\]]+$ dump_date: type: bool default: true env: DB_NAME: "${param.database}" DB_USER: "${vars.db.user}" MYSQL_PWD: "${vars.db.password}" files: dump: access: write path: "${param.dump_dir}/${param.database}{{ if .Params.dump_date }}_{{ now | date \"2006-01-02\" }}{{ end }}.sql.gz" mkdir: true overwrite: true on_error: remove env: DUMP_FILE script: path: workspace/scripts/db/dump-create.sh shell: bash messages: success: "Database dump created at ${files.dump.path}" error: "Failed to create database dump"Файловая спецификация чтения с fallback-ом
Заголовок раздела «Файловая спецификация чтения с fallback-ом»db.dump-deploy: type: script confirmation: true confirmation_text: "This will DROP and recreate `${param.target_database}`. Continue?" params: target_database: default_from: vars.db.database required: true dump_dir: default_from: vars.db.backup_dir required: true files: dump: access: read candidates: - glob: "${param.dump_dir}/${param.target_database}_*.sql.gz" match: '\d{4}-\d{2}-\d{2}' sort: name_desc - path: "${param.dump_dir}/${param.target_database}.sql.gz" required: true env: DUMP_FILE script: path: workspace/scripts/db/dump-deploy.sh shell: bashСценарий с условными шагами и шагом подтверждения
Заголовок раздела «Сценарий с условными шагами и шагом подтверждения»reset-and-bootstrap: type: workflow description: Drop, recreate, and bootstrap the main service steps: - confirm: "Drop and re-bootstrap `${vars.db.database}`?"
- command: db.drop with: database: "${vars.db.database}"
- command: services.main.db.create
- command: services.main.composer-install when: "file-missing services/main/src/vendor/autoload.php"
- command: services.main.migrate
- command: optional-cache-warm continue_on_error: trueПриватная композиция
Заголовок раздела «Приватная композиция»db.up: type: dwe private: true description: Start the database container in the background cmd: "docker up db"
db.wait: type: builtin private: true description: Wait for the db container to become healthy cmd: docker_wait_healthy with: services: [db] timeout: 120s interval: 2s
db.start: type: workflow private: true description: Start the database container and wait until healthy steps: - command: db.up - command: db.waitdb.start нельзя вызвать напрямую через dwe commands db.start, но bootstrap может ссылаться на неё из своих steps:. Композиция выше — каноничный шаблон: тонкий type: dwe для запуска, type: builtin для ожидания и type: workflow, связывающий их вместе.
Кастомные имена сетей + частичный
up/run. Если ваш compose-файл задаёт сети явноеnetworks.<x>.name:, учитывайте квирк docker-compose: labels сети выигрывает та команда, которая первой её материализует. Частичныйdocker up dbили шагtype: service_run(запускающийdocker compose run --rm --no-deps …), выполненный до полного подъёма стека, может создать именованную сеть с labels, которые последующийup --waitзатем отвергнет (network <x> … has incorrect label com.docker.compose.network). Это поведение самого compose, а не баг DWE — DWE не объявляет собственных сетей и передаёт один и тот же project/-fво все вызовы. Решение — порядок: поднимите весь стек (docker up --wait, встроенная финальная фаза деплоя) до любого частичногоup <svc>илиrun --rm, который трогает кастомно-именованную сеть, либо уберите явноеname:и дайте compose заскоупить сеть на проект.
Связанные команды
Заголовок раздела «Связанные команды»dwe commands list— перечислить все публичные команды, сгруппированные по файламdwe commands <id> [--set k=v] [--yes] [-- <args>]— выполнить команду (псевдоним:dwe cmd <id>). Всё, что после--, предлагается команде как${args}— включается для каждой команды отдельно, см. директивы § Сквозные аргументыdwe commands --inspect <id>(или-i) — показать разрешённое определение (params, context, env, runner)dwe docs generate— перегенерировать справочник по командам вdocs/reference/commands/
Когда dwe commands вызывается без точного идентификатора команды на интерактивном терминале — и без --inspect, которому идентификатор обязателен, — открывается интерактивный двухпанельный браузер команд; см. Интерактивный браузер.
--inspect / -i взаимоисключающие с --set и --yes; требует точного идентификатора команды и печатает определение, не запуская её.
JSON-листинг
Заголовок раздела «JSON-листинг»dwe commands list [group] --output json печатает объект, поле commands которого — один плоский массив, отсортированный по id. Необязательный group сужает его до этой группы и всего, что в неё вложено (admin совпадает с admin.lint, но не с administration):
{"commands":[ {"id":"app.install","group":"app","title":"install","description":"Install application dependencies","summary":"Install application dependencies","type":"service_exec","service":"app-main","params":[{"name":"env","type":"string","required":true}]}, {"id":"db.migrate","group":"db","title":"migrate","description":"Run database migrations","summary":"Run database migrations","type":"shell"}]}| Ключ | Примечания |
|---|---|
id, title, type | есть всегда; title — последний сегмент id |
group | опускается для команды вне групп |
description | опускается, если пусто. Полный текст. Локализуется: при неанглийской локали содержит перевод из workspace/i18n/<lang>.yml, поэтому сопоставляйте команды по id, а не по этому тексту |
summary | опускается, если пусто. Первая непустая строка description — см. Описание и краткая строка. dwe commands -i <id> --output json содержит ту же пару |
service | опускается, если команда его не объявляет. Это объявленное значение, а не отрендеренное — шаблонный service: app-${param.service} печатается как есть, так что это не всегда буквальное имя контейнера. Команды .start / .logs / .stop / .restart, в которые разворачивается type: daemon, несут собственный сервис демона |
private | только с --all |
params | опускается, если параметров нет; полное описание параметров — в dwe commands -i <id> --output json |
description и service позволяют агенту выбрать команду по одному листингу: чтобы найти то, что выполняется в контейнере, фильтруйте по service; чтобы понять назначение команды, читайте description. Команды, скрытые активным hide:, отсутствуют — как и в текстовом листинге.
Интерактивный браузер
Заголовок раздела «Интерактивный браузер»Браузер работает на общем фреймворке tui: две панели с рамками (дерево слева, список команд справа), нижняя строка статуса (бренд · breadcrumb · ? help) и модальная справка по ?, которая перечисляет биндинги, активные в текущем режиме. Фокус панелей переключается по Tab / Shift+Tab; сфокусированная панель подсвечивается своей рамкой. Мышь поддерживается (см. Мышь).
Он не конфигурируется: группы верхнего уровня раскрыты на входе, пустые поддеревья сворачиваются при fuzzy-фильтрации, бэйджи типов включены. Это описание полного двухпанельного фрейма; на терминале уже 100 колонок бэйджи и счётчики групп отключаются, а ниже 80 колонок или 15 строк браузер деградирует до плоского списка — см. Fallback-лестница.
Фокус переключается между двумя панелями по Tab / Shift+Tab. Биндинги навигации и выбора диспетчеризуются в сфокусированную панель; биндинги, специфичные для дерева (→/l, ←/h), инертны в списке, а run-режимные глаголы (e, y) отсутствуют вне run-режима. Модалка ? перечисляет ровно те биндинги, что активны в текущем режиме.
| Биндинг | Действие |
|---|---|
Tab / Shift+Tab | переключить фокус между панелью дерева и списка |
↑/↓, k/j | переместить курсор в сфокусированной панели |
→, l | раскрыть фокусированную группу или войти в её первого ребёнка (только дерево; no-op в списке) |
←, h | свернуть фокусированную группу или перейти к её родителю (только дерево; no-op в списке) |
Home / End | перейти к первой / последней строке сфокусированной панели |
PgUp / PgDn | проскроллить сфокусированную панель на один вьюпорт |
Enter | на группе дерева: переключить раскрытие; на элементе списка: подтвердить выбор (запуск, inspect или edit, в зависимости от точки входа) |
/ | войти в режим inline-фильтра |
i | открыть inspect-оверлей для подсвеченной команды |
y | переключить skip-confirm (только run-режим); строка статуса показывает [--yes ON] |
e | подтвердить подсвеченную команду и принудительно открыть форму параметров (только run-режим) |
? | переключить модальную справку |
Esc, q, Ctrl+C | выйти из браузера |
Пока активен inline-фильтр (/), напечатанные символы уточняют fuzzy-запрос — строка запроса рендерится внутри панели дерева, дерево сужается на лету с обновлёнными счётчиками совпадений M/N, Enter фиксирует отфильтрованное раскрытие, а Esc восстанавливает прежнее состояние раскрытия. Буквы-действия (i, e, y, …) при фильтрации печатаются в запрос, а не диспетчеризуются.
Выберите результат поиска клавишами ↑/↓. Первый Enter закрывает запрос и переводит фокус в группу команды, сохраняя выделение этой же команды в полном списке группы. Повторный Enter подтверждает выбор; само закрытие поиска команду не запускает.
Пока открыт inspect-оверлей (i), он захватывает ввод: ↑/↓, k/j, PgUp/PgDn, Home/End скроллят центрированный вьюпорт, Enter подтверждает (возвращает тот же Result, что и Enter из списка), а Esc закрывает оверлей.
Форма параметров (in-TUI оверлей)
Заголовок раздела «Форма параметров (in-TUI оверлей)»На пути с двухпанельным фреймом (TTY ≥ 80 колонок) выбор команды с параметрами открывает форму параметров как оверлей поверх браузера — браузер затемняется под ней, строка статуса остаётся видимой — вместо того чтобы сначала разбирать TUI. Вы заполняете параметры на месте; многополевые формы скроллятся внутри оверлея (сфокусированное поле остаётся видимым). При отправке оверлей закрывается, браузер выходит, и команда запускается в обычном терминале (баннер + потоковый вывод) с введёнными значениями. Esc отменяет форму и возвращает вас в браузер без запуска команды, сохраняя состояние курсора / раскрытия / фильтра; Ctrl+C выходит из всего TUI.
Enter автоматически пропускает форму, когда у каждого обязательного параметра уже есть значение (из --set или объявленного дефолта) — команда запускается сразу без оверлея. Используйте e (edit-parameters), чтобы принудительно открыть форму, даже когда обязательные значения уже удовлетворены. Команда без параметров всегда запускается сразу по Enter.
Команда всё равно исполняется после выхода из TUI (она стримит вывод docker / пайплайна в обычный терминал); в оверлей переезжает только ввод параметров. Промпт confirmation:, если он объявлен, по-прежнему показывает своё yes/no в обычном терминале после выхода из TUI, непосредственно перед запуском. На узком (< 80 колонок) fallback браузер сохраняет плоский поток exit-then-form. Прямые вызовы dwe commands <id> (с --set или без), piped / неинтерактивные и --output json не затрагиваются. (Голый dwe commands --set … без id на TTY по-прежнему открывает браузер — значения --set просто предзаполняют оверлей.)
Биндинги e (edit-parameters) и y (skip-confirm) регистрируются только в run-режиме (дефолт, когда --inspect / -i не задан); в inspect-режиме и в edit-режиме vars-браузера они отсутствуют и в keymap, и в справке ?.
Fallback-лестница
Заголовок раздела «Fallback-лестница»Браузер инспектирует терминал на старте и грациозно деградирует. Ниже минимума для двух панелей (или при ошибке чтения размера) он сразу падает на плоский список huh.NewSelect — in-TUI режима с одной панелью больше нет.
| Условие | Поведение |
|---|---|
| non-TTY | вызывающее место завершается с существующей ошибкой no exact command ID given; pass a full command ID or run in an interactive terminal; до браузера дело не доходит (а Run защитно отменяет, если всё же дошло) |
TTY, width < 80 или height < 15 | делегирует плоскому списку huh.NewSelect (pre-browser UX) |
TTY, width ∈ [80, 99] | two-panel фрейм без счётчиков групп (N) и без бэйджей типов |
TTY, width ≥ 100 | полный two-panel фрейм с бэйджами и счётчиками (breadcrumb всегда показан в нижней строке статуса) |
NO_COLOR=1 учитывается автоматически через lipgloss/bubbletea: бэйджи рендерятся как простой текст, focus-маркер использует bold вместо цвета.
Фрейм включает поддержку мыши:
- Одиночный клик в панели перемещает курсор на кликнутую строку и ставит фокус на эту панель; он никогда не переключает группу и не запускает команду.
- Двойной клик работает как
Enterна кликнутой строке — переключает группу дерева или подтверждает элемент списка. - Колесо скроллит панель под указателем (дерево, список команд или открытый оверлей инспекта), не меняя фокус — работает независимо от того, какая панель сфокусирована.
- Клик по подсказке
? helpв строке статуса переключает модальную справку; клики внутри открытой модалки проглатываются.
Дополнительное чтение
Заголовок раздела «Дополнительное чтение»- directives.md — общие поля: идентичность, подтверждение, сообщения, уведомления, params, context, env, files
- types.md — восемь типов команд и их специфичные поля, плюс разрешение workdir
- templating.md — контекст рендера, резолверы уровня команды, справочник по template-пространству
- validation.md — шпаргалка по правилам валидации и типичные подводные камни
styles.md— ключи палитры, используемые type-бэйджами браузера