Перейти к содержимому

dwe vars — работа с песочницей vars:

vars: — единственный формализованный дом для произвольных, специфичных для проекта значений в объединённом 3-слойном конфиге (см. workspace.md → Строгий корень + песочница vars:). Поскольку теперь каждое пользовательское значение живёт под одним namespace, dwe vars — это первоклассный инструмент для перечисления, чтения, редактирования и трассировки этих значений.

Var — это листовой dot-path под vars: — например, vars.db.password. Вложенные map’ы — это namespace’ы (узлы дерева), а не vars. Для:

vars:
db:
user: root
password: secret
app:
timeout: 30

листья (vars) — это vars.db.user, vars.db.password и vars.app.timeout; vars.db и vars.app — namespace’ы.

Каждый var разрешается по трём слоям конфига:

СлойИсточникЗначение
defaultworkspace.yml / defaults.ymlТрекаемый, общекомандный дефолт
localworkspace/local.ymlGitignore’нутый, персональный override
currentпосле mergeТо, во что реально разрешается ${vars.x} (local побеждает)

Origin var’а — это наивысший слой, дающий значение, то есть файл, который «выигрывает» merge для этого пути.

Префикс vars. необязателен. Внутри dwe vars любой путь — это var, поэтому get, set, inspect и фильтр namespace у list принимают сокращённую форму: dwe vars get db.host эквивалентно dwe vars get vars.db.host. Префикс также убирается из вывода (в JSON, автодополнении и хранилище остаётся канонический путь). Голова не из vars нормализуется внутрь песочницы (project.namevars.project.name) и никогда не резолвится в реальный конфиг проекта — так что ограничение на чтение и allowlist на запись из контейнера сохраняются.

dwe vars list [namespace]

Плоский список каждого листа vars.* с его эффективным значением и бейджем слоя (local, когда действует override из local.yml, иначе default). Необязательный аргумент namespace фильтрует поддерево (например, dwe vars list db), зеркаля dwe commands list.

dwe vars get <var>

Вывести одно значение. Листовой путь печатает скаляр; путь-namespace (vars.db) печатает всё поддерево как YAML. Показывается эффективное (после merge) значение. Путь, который ни во что не разрешается, — это типизированная ошибка vars_not_found. Чтение ограничено vars.* — путь, чей первый сегмент не vars (например, project.name), возвращает vars_not_found, а не разрешается по остальному конфигу проекта, чтобы доступная из контейнера поверхность vars не утекала произвольный конфиг хоста.

dwe vars inspect <var>

Полная картина по одному var’у:

  • Значения по слоям — default (общекомандный), local-override, current (после merge).
  • Origin — относительный к проекту файл, выигрывающий merge.
  • Каждое статическое использование — каждое место ссылки на var, как file:line с текстом совпавшей строки. См. статическое сканирование использований.

Var, который нигде не разрешается и не имеет использований, — это vars_not_found. Inspect сопоставляет как точный путь, так и префикс namespace: dwe vars inspect db показывает использования vars.db.host, vars.db.user и т. д. Как и get, инспекция ограничена vars.* — путь не из vars даёт vars_not_found.

dwe vars set <var> [value]

Записать override var’а в workspace/local.yml, сохраняя окружающие комментарии и форматирование (см. сохраняющая комментарии запись).

  • Ограничение пути до vars.* — любой <var>, чей первый сегмент не vars, отклоняется с vars_path_invalid. Это также граница доверия контейнера (см. поведение в контейнере).

  • Приведение значения — аргумент value парсится как один YAML-скаляр. Типизированные скаляры становятся типизированными: true / false → bool, 42 → int, 1.5 → float, простой текст → строка. Явное кавычивание оставляет строку: set x '"42"' записывает строку "42". Map’ы ({a: b}) и последовательности ([a]) отклоняются — var это лист. Закреплённые неоднозначные случаи:

    ВводРезультат
    "" (пустой аргумент, съеден shell)YAML null
    '""' (явный пустой литерал)пустая строка
    null, ~YAML null
    yes / no / on / offстрока (не bool YAML 1.1)
    0755, 01 (ведущий ноль)строка (без потери при octal-реинтерпретации)
    1.2.3строка
    2024-01-02 (голый timestamp)строка
  • Без value (интерактивно) — открывает форму huh с одним полем ввода и контекстом в стиле inspect (текущие значения по слоям). Submit пишет через тот же путь. В JSON / неинтерактивном режиме опускание value — это типизированная ошибка vars_value_required (форма не открывается).

set захватывает проектные локи (симметрия с dwe services enable/disable, которые делят тот же writer local.ymlset без локов мог бы гонками пересечься с держащим лок toggle на том же файле). Он не запускает preflight (это не мутация lifecycle/стека). При любой ошибке после записи прежние байты local.yml восстанавливаются, затем конфиг перезагружается, чтобы последующий вывод отражал запись.

Запуск dwe vars без подкоманды открывает интерактивный браузер (тот же виджет, что и у dwe commands): дерево namespace’ов всех vars, оверлей inspect и действие edit (Enter на листе).

Редактирование без выхода (терминалы ≥ 80 колонок). Enter открывает форму set как оверлей поверх браузера — дерево остаётся видимым (затемнённым) под ней. Введите новое значение (некорректный ввод — map, последовательность, непреобразуемый скаляр — отклоняется на месте), нажмите Enter для сохранения, и оверлей закроется: отредактированная строка обновится на месте (новое значение + бейдж слоя), оверлей inspect отразит новое значение, а в строке статуса на ~2 секунды мигнёт подтверждение ✓ <path> = <value>. esc отменяет и возвращает в браузер с сохранённым состоянием (курсор, раскрытие, фильтр); ctrl+c завершает весь браузер. Если блокировки проекта заняты (например, приостановленный dwe deploy run), сохранение не проходит и в строке статуса мигает ошибка блокировки — браузер остаётся открытым, а local.yml не изменяется.

Два наблюдаемых поведения отличаются от отдельного dwe vars set:

  • Подтверждения — это временная вспышка в строке статуса, а не stdout. После выхода из браузера в терминале нет следов правок — при выходе ничего не печатается. (Используйте dwe vars get <path> или заново откройте браузер, чтобы проверить значение.)
  • Редактирование без выхода работает только на пути оверлея ≥ 80 колонок. На более узком терминале браузер использует плоский запасной селектор, который выходит, чтобы запустить форму set, и затем открывается заново — старый цикл «выход после коммита», по одной правке за раз.

В неинтерактивном контексте — нет TTY, DWE_NONINTERACTIVE=1 или запуск внутри контейнера — голая команда откатывается к dwe vars list. Аргумент namespace (dwe vars vars.db) тоже выводит список, а не браузер.

dwe vars set пишет workspace/local.yml через round-trip yaml.Node: загружает файл как дерево узлов, патчит только целевой путь и сериализует заново. Комментарии, пустые строки и порядок ключей сохраняются; меняется только отредактированный узел значения. Приведение учитывается на уровне узла — перезапись закавыченной строки на true/42 выдаёт голый скаляр, чтобы он перезагрузился типизированным.

Этот writer стоит за dwe vars set, dwe services enable/disable и мастером настройки, так что комментарии в local.yml переживают любую правку под управлением DWE. Коллизии map-над-скаляром отклоняются (чтобы не отбрасывать молча данные разработчика), с единственным задокументированным исключением — легаси голый-int порт-лист апгрейдится до map’а {port: N}.

inspect (и оверлей inspect в браузере) сообщает каждое место, где на var статически ссылаются. Сканирование field-aware, а не сплошной grep по файлу — оно обходит каждый конфиг-файл и инспектирует только те поля, которые runtime реально рендерит, поэтому избегает ложных срабатываний (комментарии, закавыченные литералы, не рендерящиеся поля) и ложных пропусков (ссылки в структурных ключах). Отслеживаются два синтаксиса ссылок:

  1. Шаблонные ссылки ${vars.x} — в полях, рендерящихся движком ${...} (декларативные команды cmd / argv / compose_args / argv_append_from / env / with, шаги пайплайна timeout / files_gate.command, info.yml text / value, скалярный when:, docker.yml project_name, подтверждающие промпты) и в config-шаблонах рендера под workspace/templates/config/** (произвольный текст → построчный скан и ${vars.x}, и {{ resolve .Raw "vars.x" }}). Это шаблоны, которые материализует подсистема config-рендера; соседние пакеты ide / ai / git используют сырой субстрат {{ }} (без разрешения ${...}) и не сканируются. Внутренние пробелы (${ vars.x }) и ведущая цифра не совпадают — сканер переиспользует собственный паттерн рендерера.
  2. Структурные dot-path’ы vars.x — значения from: / default_from: и ссылки внутри типизированного when.expr (не голый скаляр when: vars.x).

Сопоставление по точному пути или префиксу namespace: ${vars.db.host} засчитывается и vars.db.host, и vars.db.

Сам блок vars: верхнего уровня не сканируется: его значения — это данные конфига, разрешаемые по dot-path и никогда не рендерящиеся повторно, поэтому ${vars.x} или from:, появляющиеся внутри vars:, не являются использованием во время выполнения.

Оговорка (печатается в выводе): динамически собираемые пути и доступ к полям через Go-шаблоны (.Vars.x / .Raw.vars.x в шаблонах {{ ... }}) не отслеживаются. Сканирование покрывает только формы ${...} и структурный-dot-path.

Каждая read-only подкоманда поддерживает --output json--pretty) и держит stdout чистым — типизированные ошибки сериализуются в конверт {"error":{…}} на stderr.

КомандаФорма
get{"var": "...", "value": <any>}
list{"vars": [{"path": "...", "value": <any>, "layer": "local|default"}]}
inspect{"var": "...", "layers": {"default": <any>, "default_set": <bool>, "local": ..., "local_set": ..., "current": ..., "current_set": ...}, "origin": "...", "usages": [{"file": "...", "line": N, "kind": "...", "text": "..."}]}
set (со значением){"var": "...", "value": <any>}

Булевы *_set на слоях inspect отличают явное значение null от отсутствующего слоя. set без значения в JSON-режиме — это ошибка vars_value_required (форма не открывается).

dwe vars достижима изнутри bridged-контейнера, но записи запрещены по умолчанию (deny-by-default):

  • get / list / inspect достижимы всегда.
  • set достижима, но контейнерный set успешен только когда целевой var совпадает с записью в allowlist’е проекта bridge.vars_writable. Несовпадающий var отклоняется с vars_not_container_writable. С хоста set неограничен.
  • TUI авто-отключается в контейнере (неинтерактивный откат к list).

bridge.vars_writable — это top-level конфиг-блок, отличный от per-service блока services.<name>.bridge: в service.yml, который управляет включением контейнера. Этот top-level блок — это общепроектная политика контейнерной записи: в какие vars контейнеризованный dwe vars set может писать в хостовый local.yml.

# workspace.yml (или любой слой — 3-слойный merge)
bridge:
vars_writable:
- vars.db.password # точный путь
- vars.app.* # префиксный wildcard (граница по точке)

bridge входит в строгий корневой allowlist; он мёржится по трём слоям, как любой другой формализованный блок. Семантика паттернов использует реальную границу по точке, а не наивное префиксное совпадение:

  • Точный паттерн (vars.db.password) совпадает только с этим идентичным путём.
  • Wildcard vars.x.* совпадает с target только когда target начинается с base + ".". Так vars.db.* разрешает vars.db.host, но запрещает vars.db, vars.dbx.host и vars.database.host.

Пустой или отсутствующий список vars_writable означает отсутствие контейнерных записей — безопасный дефолт. Некорректные паттерны fail closed (запрет).

Что на самом деле даёт добавление вара в allowlist. Поля шага пайплайна (cmd:, when.cmd, check.cmd, timeout:) рендерятся через подстановку ${...}, а отрендеренный cmd: уходит в хостовый sh -c как текст программы — подстановка текстовая, без shell-квотирования (см. Шаблоны в полях шага). Поэтому вар, который одновременно доступен на запись из контейнера и используется в команде пайплайна, позволяет контейнеру выбрать часть хостовой командной строки: значение x; some-command выполнит some-command на хосте при следующем dwe deploy run. Это и есть естественная комбинация — контейнер обычно хочет записать вар именно потому, что его читает пайплайн, — так что считайте vars_writable делегированием ввода в хостовый shell тому, кто имеет доступ к контейнеру, и держите в списке вары, которые потребляются как данные (рендер конфигов, exports.env), а не подставляются в команды.

Чтобы контейнер мог регенерировать конфиги сервисов после set, dwe render config тоже достижима из контейнера (остальные подкоманды render — env / ide / ai / git — остаются только хостовыми). Поскольку render config --harvest мутирует хостовое состояние (.dwe/generated.yml), контейнерный render config --harvest отклоняется с render_harvest_host_only — границу пересекает только read-only render.

  • dwe vars get <var> — вывести эффективное значение var’а
  • dwe vars list [namespace] — перечислить листья vars.*
  • dwe vars inspect <var> — значения по слоям, origin и использования
  • dwe vars set <var> [value] — записать override в local.yml (с сохранением комментариев)
  • dwe render env / dwe render config — регенерировать .env / конфиги сервисов из объединённого конфига
  • dwe services enable / disable — переключатели сервисов (с сохранением комментариев)